mirror of
				https://github.com/Frooodle/Stirling-PDF.git
				synced 2025-11-01 01:21:18 +01:00 
			
		
		
		
	Merge pull request #1201 from Ludy87/add_functions_oauth2
extends the functionality of oauth in Stirling PDF
This commit is contained in:
		
						commit
						6fc9a2032a
					
				@ -15,7 +15,14 @@ public class CleanUrlInterceptor implements HandlerInterceptor {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private static final List<String> ALLOWED_PARAMS =
 | 
					    private static final List<String> ALLOWED_PARAMS =
 | 
				
			||||||
            Arrays.asList(
 | 
					            Arrays.asList(
 | 
				
			||||||
                    "lang", "endpoint", "endpoints", "logout", "error", "file", "messageType");
 | 
					                    "lang",
 | 
				
			||||||
 | 
					                    "endpoint",
 | 
				
			||||||
 | 
					                    "endpoints",
 | 
				
			||||||
 | 
					                    "logout",
 | 
				
			||||||
 | 
					                    "error",
 | 
				
			||||||
 | 
					                    "erroroauth",
 | 
				
			||||||
 | 
					                    "file",
 | 
				
			||||||
 | 
					                    "messageType");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public boolean preHandle(
 | 
					    public boolean preHandle(
 | 
				
			||||||
 | 
				
			|||||||
@ -3,27 +3,31 @@ package stirling.software.SPDF.config.security;
 | 
				
			|||||||
import java.io.IOException;
 | 
					import java.io.IOException;
 | 
				
			||||||
import java.util.Optional;
 | 
					import java.util.Optional;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
					import org.slf4j.Logger;
 | 
				
			||||||
 | 
					import org.slf4j.LoggerFactory;
 | 
				
			||||||
import org.springframework.security.authentication.BadCredentialsException;
 | 
					import org.springframework.security.authentication.BadCredentialsException;
 | 
				
			||||||
 | 
					import org.springframework.security.authentication.InternalAuthenticationServiceException;
 | 
				
			||||||
import org.springframework.security.authentication.LockedException;
 | 
					import org.springframework.security.authentication.LockedException;
 | 
				
			||||||
import org.springframework.security.core.AuthenticationException;
 | 
					import org.springframework.security.core.AuthenticationException;
 | 
				
			||||||
 | 
					import org.springframework.security.core.userdetails.UsernameNotFoundException;
 | 
				
			||||||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
 | 
					import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
 | 
				
			||||||
import org.springframework.stereotype.Component;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import jakarta.servlet.ServletException;
 | 
					import jakarta.servlet.ServletException;
 | 
				
			||||||
import jakarta.servlet.http.HttpServletRequest;
 | 
					import jakarta.servlet.http.HttpServletRequest;
 | 
				
			||||||
import jakarta.servlet.http.HttpServletResponse;
 | 
					import jakarta.servlet.http.HttpServletResponse;
 | 
				
			||||||
import stirling.software.SPDF.model.User;
 | 
					import stirling.software.SPDF.model.User;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Component
 | 
					 | 
				
			||||||
public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
 | 
					public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Autowired private final LoginAttemptService loginAttemptService;
 | 
					    private LoginAttemptService loginAttemptService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Autowired private final UserService userService; // Inject the UserService
 | 
					    private UserService userService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static final Logger logger =
 | 
				
			||||||
 | 
					            LoggerFactory.getLogger(CustomAuthenticationFailureHandler.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public CustomAuthenticationFailureHandler(
 | 
					    public CustomAuthenticationFailureHandler(
 | 
				
			||||||
            LoginAttemptService loginAttemptService, UserService userService) {
 | 
					            final LoginAttemptService loginAttemptService, UserService userService) {
 | 
				
			||||||
        this.loginAttemptService = loginAttemptService;
 | 
					        this.loginAttemptService = loginAttemptService;
 | 
				
			||||||
        this.userService = userService;
 | 
					        this.userService = userService;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -34,22 +38,33 @@ public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationF
 | 
				
			|||||||
            HttpServletResponse response,
 | 
					            HttpServletResponse response,
 | 
				
			||||||
            AuthenticationException exception)
 | 
					            AuthenticationException exception)
 | 
				
			||||||
            throws IOException, ServletException {
 | 
					            throws IOException, ServletException {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        String ip = request.getRemoteAddr();
 | 
					        String ip = request.getRemoteAddr();
 | 
				
			||||||
        logger.error("Failed login attempt from IP: " + ip);
 | 
					        logger.error("Failed login attempt from IP: {}", ip);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (exception.getClass().isAssignableFrom(InternalAuthenticationServiceException.class)
 | 
				
			||||||
 | 
					                || "Password must not be null".equalsIgnoreCase(exception.getMessage())) {
 | 
				
			||||||
 | 
					            response.sendRedirect("/login?error=oauth2AuthenticationError");
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        String username = request.getParameter("username");
 | 
					        String username = request.getParameter("username");
 | 
				
			||||||
        if (!isDemoUser(username)) {
 | 
					        if (username != null && !isDemoUser(username)) {
 | 
				
			||||||
            if (loginAttemptService.loginAttemptCheck(username)) {
 | 
					            logger.info(
 | 
				
			||||||
                setDefaultFailureUrl("/login?error=locked");
 | 
					                    "Remaining attempts for user {}: {}",
 | 
				
			||||||
 | 
					                    username,
 | 
				
			||||||
            } else {
 | 
					                    loginAttemptService.getRemainingAttempts(username));
 | 
				
			||||||
                if (exception.getClass().isAssignableFrom(LockedException.class)) {
 | 
					            loginAttemptService.loginFailed(username);
 | 
				
			||||||
                    setDefaultFailureUrl("/login?error=locked");
 | 
					            if (loginAttemptService.isBlocked(username)
 | 
				
			||||||
                }
 | 
					                    || exception.getClass().isAssignableFrom(LockedException.class)) {
 | 
				
			||||||
 | 
					                response.sendRedirect("/login?error=locked");
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (exception.getClass().isAssignableFrom(BadCredentialsException.class)) {
 | 
					        if (exception.getClass().isAssignableFrom(BadCredentialsException.class)
 | 
				
			||||||
            setDefaultFailureUrl("/login?error=badcredentials");
 | 
					                || exception.getClass().isAssignableFrom(UsernameNotFoundException.class)) {
 | 
				
			||||||
 | 
					            response.sendRedirect("/login?error=badcredentials");
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        super.onAuthenticationFailure(request, response, exception);
 | 
					        super.onAuthenticationFailure(request, response, exception);
 | 
				
			||||||
 | 
				
			|||||||
@ -2,11 +2,9 @@ package stirling.software.SPDF.config.security;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import java.io.IOException;
 | 
					import java.io.IOException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
					 | 
				
			||||||
import org.springframework.security.core.Authentication;
 | 
					import org.springframework.security.core.Authentication;
 | 
				
			||||||
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
 | 
					import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
 | 
				
			||||||
import org.springframework.security.web.savedrequest.SavedRequest;
 | 
					import org.springframework.security.web.savedrequest.SavedRequest;
 | 
				
			||||||
import org.springframework.stereotype.Component;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import jakarta.servlet.ServletException;
 | 
					import jakarta.servlet.ServletException;
 | 
				
			||||||
import jakarta.servlet.http.HttpServletRequest;
 | 
					import jakarta.servlet.http.HttpServletRequest;
 | 
				
			||||||
@ -14,25 +12,30 @@ import jakarta.servlet.http.HttpServletResponse;
 | 
				
			|||||||
import jakarta.servlet.http.HttpSession;
 | 
					import jakarta.servlet.http.HttpSession;
 | 
				
			||||||
import stirling.software.SPDF.utils.RequestUriUtils;
 | 
					import stirling.software.SPDF.utils.RequestUriUtils;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Component
 | 
					 | 
				
			||||||
public class CustomAuthenticationSuccessHandler
 | 
					public class CustomAuthenticationSuccessHandler
 | 
				
			||||||
        extends SavedRequestAwareAuthenticationSuccessHandler {
 | 
					        extends SavedRequestAwareAuthenticationSuccessHandler {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Autowired private LoginAttemptService loginAttemptService;
 | 
					    private LoginAttemptService loginAttemptService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public CustomAuthenticationSuccessHandler(LoginAttemptService loginAttemptService) {
 | 
				
			||||||
 | 
					        this.loginAttemptService = loginAttemptService;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public void onAuthenticationSuccess(
 | 
					    public void onAuthenticationSuccess(
 | 
				
			||||||
            HttpServletRequest request, HttpServletResponse response, Authentication authentication)
 | 
					            HttpServletRequest request, HttpServletResponse response, Authentication authentication)
 | 
				
			||||||
            throws ServletException, IOException {
 | 
					            throws ServletException, IOException {
 | 
				
			||||||
        String username = request.getParameter("username");
 | 
					
 | 
				
			||||||
        loginAttemptService.loginSucceeded(username);
 | 
					        String userName = request.getParameter("username");
 | 
				
			||||||
 | 
					        loginAttemptService.loginSucceeded(userName);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Get the saved request
 | 
					        // Get the saved request
 | 
				
			||||||
        HttpSession session = request.getSession(false);
 | 
					        HttpSession session = request.getSession(false);
 | 
				
			||||||
        SavedRequest savedRequest =
 | 
					        SavedRequest savedRequest =
 | 
				
			||||||
                session != null
 | 
					                (session != null)
 | 
				
			||||||
                        ? (SavedRequest) session.getAttribute("SPRING_SECURITY_SAVED_REQUEST")
 | 
					                        ? (SavedRequest) session.getAttribute("SPRING_SECURITY_SAVED_REQUEST")
 | 
				
			||||||
                        : null;
 | 
					                        : null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (savedRequest != null
 | 
					        if (savedRequest != null
 | 
				
			||||||
                && !RequestUriUtils.isStaticResource(savedRequest.getRedirectUrl())) {
 | 
					                && !RequestUriUtils.isStaticResource(savedRequest.getRedirectUrl())) {
 | 
				
			||||||
            // Redirect to the original destination
 | 
					            // Redirect to the original destination
 | 
				
			||||||
 | 
				
			|||||||
@ -2,10 +2,9 @@ package stirling.software.SPDF.config.security;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import java.io.IOException;
 | 
					import java.io.IOException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.springframework.context.annotation.Bean;
 | 
					import org.springframework.beans.factory.annotation.Autowired;
 | 
				
			||||||
import org.springframework.security.core.Authentication;
 | 
					import org.springframework.security.core.Authentication;
 | 
				
			||||||
import org.springframework.security.core.session.SessionRegistry;
 | 
					import org.springframework.security.core.session.SessionRegistry;
 | 
				
			||||||
import org.springframework.security.core.session.SessionRegistryImpl;
 | 
					 | 
				
			||||||
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
 | 
					import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import jakarta.servlet.ServletException;
 | 
					import jakarta.servlet.ServletException;
 | 
				
			||||||
@ -14,10 +13,8 @@ import jakarta.servlet.http.HttpServletResponse;
 | 
				
			|||||||
import jakarta.servlet.http.HttpSession;
 | 
					import jakarta.servlet.http.HttpSession;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
 | 
					public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
 | 
				
			||||||
    @Bean
 | 
					
 | 
				
			||||||
    public SessionRegistry sessionRegistry() {
 | 
					    @Autowired SessionRegistry sessionRegistry;
 | 
				
			||||||
        return new SessionRegistryImpl();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public void onLogoutSuccess(
 | 
					    public void onLogoutSuccess(
 | 
				
			||||||
@ -26,14 +23,11 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
 | 
				
			|||||||
        HttpSession session = request.getSession(false);
 | 
					        HttpSession session = request.getSession(false);
 | 
				
			||||||
        if (session != null) {
 | 
					        if (session != null) {
 | 
				
			||||||
            String sessionId = session.getId();
 | 
					            String sessionId = session.getId();
 | 
				
			||||||
            sessionRegistry().removeSessionInformation(sessionId);
 | 
					            sessionRegistry.removeSessionInformation(sessionId);
 | 
				
			||||||
 | 
					            session.invalidate();
 | 
				
			||||||
 | 
					            logger.debug("Session invalidated: " + sessionId);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (request.getParameter("oauth2AutoCreateDisabled") != null) {
 | 
					        response.sendRedirect(request.getContextPath() + "/login?logout=true");
 | 
				
			||||||
            response.sendRedirect(
 | 
					 | 
				
			||||||
                    request.getContextPath() + "/login?error=oauth2AutoCreateDisabled");
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            response.sendRedirect(request.getContextPath() + "/login?logout=true");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -39,6 +39,10 @@ public class CustomUserDetailsService implements UserDetailsService {
 | 
				
			|||||||
                    "Your account has been locked due to too many failed login attempts.");
 | 
					                    "Your account has been locked due to too many failed login attempts.");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!user.hasPassword()) {
 | 
				
			||||||
 | 
					            throw new IllegalArgumentException("Password must not be null");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return new org.springframework.security.core.userdetails.User(
 | 
					        return new org.springframework.security.core.userdetails.User(
 | 
				
			||||||
                user.getUsername(),
 | 
					                user.getUsername(),
 | 
				
			||||||
                user.getPassword(),
 | 
					                user.getPassword(),
 | 
				
			||||||
 | 
				
			|||||||
@ -7,6 +7,8 @@ import java.nio.file.Paths;
 | 
				
			|||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
import java.util.UUID;
 | 
					import java.util.UUID;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.slf4j.Logger;
 | 
				
			||||||
 | 
					import org.slf4j.LoggerFactory;
 | 
				
			||||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
					import org.springframework.beans.factory.annotation.Autowired;
 | 
				
			||||||
import org.springframework.stereotype.Component;
 | 
					import org.springframework.stereotype.Component;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -19,32 +21,16 @@ public class InitialSecuritySetup {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @Autowired private UserService userService;
 | 
					    @Autowired private UserService userService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Autowired ApplicationProperties applicationProperties;
 | 
					    @Autowired private ApplicationProperties applicationProperties;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static final Logger logger = LoggerFactory.getLogger(InitialSecuritySetup.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @PostConstruct
 | 
					    @PostConstruct
 | 
				
			||||||
    public void init() {
 | 
					    public void init() {
 | 
				
			||||||
        if (!userService.hasUsers()) {
 | 
					        if (!userService.hasUsers()) {
 | 
				
			||||||
 | 
					            initializeAdminUser();
 | 
				
			||||||
            String initialUsername =
 | 
					 | 
				
			||||||
                    applicationProperties.getSecurity().getInitialLogin().getUsername();
 | 
					 | 
				
			||||||
            String initialPassword =
 | 
					 | 
				
			||||||
                    applicationProperties.getSecurity().getInitialLogin().getPassword();
 | 
					 | 
				
			||||||
            if (initialUsername != null && initialPassword != null) {
 | 
					 | 
				
			||||||
                userService.saveUser(initialUsername, initialPassword, Role.ADMIN.getRoleId());
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                initialUsername = "admin";
 | 
					 | 
				
			||||||
                initialPassword = "stirling";
 | 
					 | 
				
			||||||
                userService.saveUser(
 | 
					 | 
				
			||||||
                        initialUsername, initialPassword, Role.ADMIN.getRoleId(), true);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (!userService.usernameExistsIgnoreCase(Role.INTERNAL_API_USER.getRoleId())) {
 | 
					 | 
				
			||||||
            userService.saveUser(
 | 
					 | 
				
			||||||
                    Role.INTERNAL_API_USER.getRoleId(),
 | 
					 | 
				
			||||||
                    UUID.randomUUID().toString(),
 | 
					 | 
				
			||||||
                    Role.INTERNAL_API_USER.getRoleId());
 | 
					 | 
				
			||||||
            userService.addApiKeyToUser(Role.INTERNAL_API_USER.getRoleId());
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        initializeInternalApiUser();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @PostConstruct
 | 
					    @PostConstruct
 | 
				
			||||||
@ -56,6 +42,51 @@ public class InitialSecuritySetup {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void initializeAdminUser() {
 | 
				
			||||||
 | 
					        String initialUsername =
 | 
				
			||||||
 | 
					                applicationProperties.getSecurity().getInitialLogin().getUsername();
 | 
				
			||||||
 | 
					        String initialPassword =
 | 
				
			||||||
 | 
					                applicationProperties.getSecurity().getInitialLogin().getPassword();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (initialUsername != null
 | 
				
			||||||
 | 
					                && !initialUsername.isEmpty()
 | 
				
			||||||
 | 
					                && initialPassword != null
 | 
				
			||||||
 | 
					                && !initialPassword.isEmpty()
 | 
				
			||||||
 | 
					                && !userService.findByUsernameIgnoreCase(initialUsername).isPresent()) {
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                if (userService.isUsernameValid(initialUsername)) {
 | 
				
			||||||
 | 
					                    userService.saveUser(initialUsername, initialPassword, Role.ADMIN.getRoleId());
 | 
				
			||||||
 | 
					                    logger.info("Admin user created: " + initialUsername);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } catch (IllegalArgumentException e) {
 | 
				
			||||||
 | 
					                logger.error("Failed to initialize security setup", e);
 | 
				
			||||||
 | 
					                System.exit(1);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            createDefaultAdminUser();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void createDefaultAdminUser() {
 | 
				
			||||||
 | 
					        String defaultUsername = "admin";
 | 
				
			||||||
 | 
					        String defaultPassword = "stirling";
 | 
				
			||||||
 | 
					        if (!userService.findByUsernameIgnoreCase(defaultUsername).isPresent()) {
 | 
				
			||||||
 | 
					            userService.saveUser(defaultUsername, defaultPassword, Role.ADMIN.getRoleId(), true);
 | 
				
			||||||
 | 
					            logger.info("Default admin user created: " + defaultUsername);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void initializeInternalApiUser() {
 | 
				
			||||||
 | 
					        if (!userService.usernameExistsIgnoreCase(Role.INTERNAL_API_USER.getRoleId())) {
 | 
				
			||||||
 | 
					            userService.saveUser(
 | 
				
			||||||
 | 
					                    Role.INTERNAL_API_USER.getRoleId(),
 | 
				
			||||||
 | 
					                    UUID.randomUUID().toString(),
 | 
				
			||||||
 | 
					                    Role.INTERNAL_API_USER.getRoleId());
 | 
				
			||||||
 | 
					            userService.addApiKeyToUser(Role.INTERNAL_API_USER.getRoleId());
 | 
				
			||||||
 | 
					            logger.info("Internal API user created: " + Role.INTERNAL_API_USER.getRoleId());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private void saveKeyToConfig(String key) throws IOException {
 | 
					    private void saveKeyToConfig(String key) throws IOException {
 | 
				
			||||||
        Path path = Paths.get("configs", "settings.yml"); // Target the configs/settings.yml
 | 
					        Path path = Paths.get("configs", "settings.yml"); // Target the configs/settings.yml
 | 
				
			||||||
        List<String> lines = Files.readAllLines(path);
 | 
					        List<String> lines = Files.readAllLines(path);
 | 
				
			||||||
 | 
				
			|||||||
@ -3,6 +3,8 @@ package stirling.software.SPDF.config.security;
 | 
				
			|||||||
import java.util.concurrent.ConcurrentHashMap;
 | 
					import java.util.concurrent.ConcurrentHashMap;
 | 
				
			||||||
import java.util.concurrent.TimeUnit;
 | 
					import java.util.concurrent.TimeUnit;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.slf4j.Logger;
 | 
				
			||||||
 | 
					import org.slf4j.LoggerFactory;
 | 
				
			||||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
					import org.springframework.beans.factory.annotation.Autowired;
 | 
				
			||||||
import org.springframework.stereotype.Service;
 | 
					import org.springframework.stereotype.Service;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -15,44 +17,62 @@ public class LoginAttemptService {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @Autowired ApplicationProperties applicationProperties;
 | 
					    @Autowired ApplicationProperties applicationProperties;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private int MAX_ATTEMPTS;
 | 
					    private static final Logger logger = LoggerFactory.getLogger(LoginAttemptService.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private int MAX_ATTEMPT;
 | 
				
			||||||
    private long ATTEMPT_INCREMENT_TIME;
 | 
					    private long ATTEMPT_INCREMENT_TIME;
 | 
				
			||||||
 | 
					    private ConcurrentHashMap<String, AttemptCounter> attemptsCache;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @PostConstruct
 | 
					    @PostConstruct
 | 
				
			||||||
    public void init() {
 | 
					    public void init() {
 | 
				
			||||||
        MAX_ATTEMPTS = applicationProperties.getSecurity().getLoginAttemptCount();
 | 
					        MAX_ATTEMPT = applicationProperties.getSecurity().getLoginAttemptCount();
 | 
				
			||||||
        ATTEMPT_INCREMENT_TIME =
 | 
					        ATTEMPT_INCREMENT_TIME =
 | 
				
			||||||
                TimeUnit.MINUTES.toMillis(
 | 
					                TimeUnit.MINUTES.toMillis(
 | 
				
			||||||
                        applicationProperties.getSecurity().getLoginResetTimeMinutes());
 | 
					                        applicationProperties.getSecurity().getLoginResetTimeMinutes());
 | 
				
			||||||
 | 
					        attemptsCache = new ConcurrentHashMap<>();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private final ConcurrentHashMap<String, AttemptCounter> attemptsCache =
 | 
					 | 
				
			||||||
            new ConcurrentHashMap<>();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public void loginSucceeded(String key) {
 | 
					    public void loginSucceeded(String key) {
 | 
				
			||||||
        attemptsCache.remove(key);
 | 
					        logger.info(key + " " + attemptsCache.mappingCount());
 | 
				
			||||||
 | 
					        if (key == null || key.trim().isEmpty()) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        attemptsCache.remove(key.toLowerCase());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public boolean loginAttemptCheck(String key) {
 | 
					    public void loginFailed(String key) {
 | 
				
			||||||
        attemptsCache.compute(
 | 
					        if (key == null || key.trim().isEmpty()) return;
 | 
				
			||||||
                key,
 | 
					
 | 
				
			||||||
                (k, attemptCounter) -> {
 | 
					        AttemptCounter attemptCounter = attemptsCache.get(key.toLowerCase());
 | 
				
			||||||
                    if (attemptCounter == null
 | 
					        if (attemptCounter == null) {
 | 
				
			||||||
                            || attemptCounter.shouldReset(ATTEMPT_INCREMENT_TIME)) {
 | 
					            attemptCounter = new AttemptCounter();
 | 
				
			||||||
                        return new AttemptCounter();
 | 
					            attemptsCache.put(key.toLowerCase(), attemptCounter);
 | 
				
			||||||
                    } else {
 | 
					        } else {
 | 
				
			||||||
                        attemptCounter.increment();
 | 
					            if (attemptCounter.shouldReset(ATTEMPT_INCREMENT_TIME)) {
 | 
				
			||||||
                        return attemptCounter;
 | 
					                attemptCounter.reset();
 | 
				
			||||||
                    }
 | 
					            }
 | 
				
			||||||
                });
 | 
					            attemptCounter.increment();
 | 
				
			||||||
        return attemptsCache.get(key).getAttemptCount() >= MAX_ATTEMPTS;
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public boolean isBlocked(String key) {
 | 
					    public boolean isBlocked(String key) {
 | 
				
			||||||
        AttemptCounter attemptCounter = attemptsCache.get(key);
 | 
					        if (key == null || key.trim().isEmpty()) return false;
 | 
				
			||||||
        if (attemptCounter != null) {
 | 
					        AttemptCounter attemptCounter = attemptsCache.get(key.toLowerCase());
 | 
				
			||||||
            return attemptCounter.getAttemptCount() >= MAX_ATTEMPTS;
 | 
					        if (attemptCounter == null) {
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return false;
 | 
					
 | 
				
			||||||
 | 
					        return attemptCounter.getAttemptCount() >= MAX_ATTEMPT;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public int getRemainingAttempts(String key) {
 | 
				
			||||||
 | 
					        if (key == null || key.trim().isEmpty()) return MAX_ATTEMPT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        AttemptCounter attemptCounter = attemptsCache.get(key.toLowerCase());
 | 
				
			||||||
 | 
					        if (attemptCounter == null) {
 | 
				
			||||||
 | 
					            return MAX_ATTEMPT;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return MAX_ATTEMPT - attemptCounter.getAttemptCount();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,5 @@
 | 
				
			|||||||
package stirling.software.SPDF.config.security;
 | 
					package stirling.software.SPDF.config.security;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.io.IOException;
 | 
					 | 
				
			||||||
import java.util.*;
 | 
					import java.util.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
					import org.springframework.beans.factory.annotation.Autowired;
 | 
				
			||||||
@ -14,33 +13,30 @@ import org.springframework.security.config.annotation.method.configuration.Enabl
 | 
				
			|||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 | 
					import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 | 
				
			||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 | 
					import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 | 
				
			||||||
import org.springframework.security.config.http.SessionCreationPolicy;
 | 
					import org.springframework.security.config.http.SessionCreationPolicy;
 | 
				
			||||||
import org.springframework.security.core.Authentication;
 | 
					 | 
				
			||||||
import org.springframework.security.core.GrantedAuthority;
 | 
					import org.springframework.security.core.GrantedAuthority;
 | 
				
			||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
 | 
					import org.springframework.security.core.authority.SimpleGrantedAuthority;
 | 
				
			||||||
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
 | 
					import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
 | 
				
			||||||
import org.springframework.security.core.session.SessionRegistry;
 | 
					import org.springframework.security.core.session.SessionRegistry;
 | 
				
			||||||
import org.springframework.security.core.session.SessionRegistryImpl;
 | 
					import org.springframework.security.core.session.SessionRegistryImpl;
 | 
				
			||||||
import org.springframework.security.core.userdetails.UserDetailsService;
 | 
					 | 
				
			||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 | 
					import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 | 
				
			||||||
import org.springframework.security.crypto.password.PasswordEncoder;
 | 
					import org.springframework.security.crypto.password.PasswordEncoder;
 | 
				
			||||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
 | 
					import org.springframework.security.oauth2.client.registration.ClientRegistration;
 | 
				
			||||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
 | 
					import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
 | 
				
			||||||
import org.springframework.security.oauth2.client.registration.ClientRegistrations;
 | 
					import org.springframework.security.oauth2.client.registration.ClientRegistrations;
 | 
				
			||||||
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
 | 
					import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
 | 
				
			||||||
import org.springframework.security.oauth2.core.user.OAuth2User;
 | 
					 | 
				
			||||||
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
 | 
					import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
 | 
				
			||||||
import org.springframework.security.web.SecurityFilterChain;
 | 
					import org.springframework.security.web.SecurityFilterChain;
 | 
				
			||||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
 | 
					 | 
				
			||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
 | 
					import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
 | 
				
			||||||
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
 | 
					import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
 | 
				
			||||||
import org.springframework.security.web.savedrequest.NullRequestCache;
 | 
					import org.springframework.security.web.savedrequest.NullRequestCache;
 | 
				
			||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 | 
					import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import jakarta.servlet.ServletException;
 | 
					import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationFailureHandler;
 | 
				
			||||||
import jakarta.servlet.http.HttpServletRequest;
 | 
					import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationSuccessHandler;
 | 
				
			||||||
import jakarta.servlet.http.HttpServletResponse;
 | 
					import stirling.software.SPDF.config.security.oauth2.CustomOAuth2LogoutSuccessHandler;
 | 
				
			||||||
import jakarta.servlet.http.HttpSession;
 | 
					import stirling.software.SPDF.config.security.oauth2.CustomOAuth2UserService;
 | 
				
			||||||
import stirling.software.SPDF.model.ApplicationProperties;
 | 
					import stirling.software.SPDF.model.ApplicationProperties;
 | 
				
			||||||
 | 
					import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
 | 
				
			||||||
import stirling.software.SPDF.model.User;
 | 
					import stirling.software.SPDF.model.User;
 | 
				
			||||||
import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
 | 
					import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -49,7 +45,7 @@ import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
 | 
				
			|||||||
@EnableMethodSecurity
 | 
					@EnableMethodSecurity
 | 
				
			||||||
public class SecurityConfiguration {
 | 
					public class SecurityConfiguration {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Autowired private UserDetailsService userDetailsService;
 | 
					    @Autowired private CustomUserDetailsService userDetailsService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Bean
 | 
					    @Bean
 | 
				
			||||||
    public PasswordEncoder passwordEncoder() {
 | 
					    public PasswordEncoder passwordEncoder() {
 | 
				
			||||||
@ -98,7 +94,8 @@ public class SecurityConfiguration {
 | 
				
			|||||||
                                    formLogin
 | 
					                                    formLogin
 | 
				
			||||||
                                            .loginPage("/login")
 | 
					                                            .loginPage("/login")
 | 
				
			||||||
                                            .successHandler(
 | 
					                                            .successHandler(
 | 
				
			||||||
                                                    new CustomAuthenticationSuccessHandler())
 | 
					                                                    new CustomAuthenticationSuccessHandler(
 | 
				
			||||||
 | 
					                                                            loginAttemptService))
 | 
				
			||||||
                                            .defaultSuccessUrl("/")
 | 
					                                            .defaultSuccessUrl("/")
 | 
				
			||||||
                                            .failureHandler(
 | 
					                                            .failureHandler(
 | 
				
			||||||
                                                    new CustomAuthenticationFailureHandler(
 | 
					                                                    new CustomAuthenticationFailureHandler(
 | 
				
			||||||
@ -111,18 +108,7 @@ public class SecurityConfiguration {
 | 
				
			|||||||
                                                    new AntPathRequestMatcher("/logout"))
 | 
					                                                    new AntPathRequestMatcher("/logout"))
 | 
				
			||||||
                                            .logoutSuccessHandler(new CustomLogoutSuccessHandler())
 | 
					                                            .logoutSuccessHandler(new CustomLogoutSuccessHandler())
 | 
				
			||||||
                                            .invalidateHttpSession(true) // Invalidate session
 | 
					                                            .invalidateHttpSession(true) // Invalidate session
 | 
				
			||||||
                                            .deleteCookies("JSESSIONID", "remember-me")
 | 
					                                            .deleteCookies("JSESSIONID", "remember-me"))
 | 
				
			||||||
                                            .addLogoutHandler(
 | 
					 | 
				
			||||||
                                                    (request, response, authentication) -> {
 | 
					 | 
				
			||||||
                                                        HttpSession session =
 | 
					 | 
				
			||||||
                                                                request.getSession(false);
 | 
					 | 
				
			||||||
                                                        if (session != null) {
 | 
					 | 
				
			||||||
                                                            String sessionId = session.getId();
 | 
					 | 
				
			||||||
                                                            sessionRegistry()
 | 
					 | 
				
			||||||
                                                                    .removeSessionInformation(
 | 
					 | 
				
			||||||
                                                                            sessionId);
 | 
					 | 
				
			||||||
                                                        }
 | 
					 | 
				
			||||||
                                                    }))
 | 
					 | 
				
			||||||
                    .rememberMe(
 | 
					                    .rememberMe(
 | 
				
			||||||
                            rememberMeConfigurer ->
 | 
					                            rememberMeConfigurer ->
 | 
				
			||||||
                                    rememberMeConfigurer // Use the configurator directly
 | 
					                                    rememberMeConfigurer // Use the configurator directly
 | 
				
			||||||
@ -161,50 +147,43 @@ public class SecurityConfiguration {
 | 
				
			|||||||
                                            .permitAll()
 | 
					                                            .permitAll()
 | 
				
			||||||
                                            .anyRequest()
 | 
					                                            .anyRequest()
 | 
				
			||||||
                                            .authenticated())
 | 
					                                            .authenticated())
 | 
				
			||||||
                    .userDetailsService(userDetailsService)
 | 
					 | 
				
			||||||
                    .authenticationProvider(authenticationProvider());
 | 
					                    .authenticationProvider(authenticationProvider());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Handle OAUTH2 Logins
 | 
					            // Handle OAUTH2 Logins
 | 
				
			||||||
            if (applicationProperties.getSecurity().getOAUTH2().getEnabled()) {
 | 
					            if (applicationProperties.getSecurity().getOAUTH2().getEnabled()) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                http.oauth2Login(
 | 
					                http.oauth2Login(
 | 
				
			||||||
                        oauth2 ->
 | 
					                                oauth2 ->
 | 
				
			||||||
                                oauth2.loginPage("/oauth2")
 | 
					                                        oauth2.loginPage("/oauth2")
 | 
				
			||||||
                                        /*
 | 
					                                                /*
 | 
				
			||||||
                                        This Custom handler is used to check if the OAUTH2 user trying to log in, already exists in the database.
 | 
					                                                This Custom handler is used to check if the OAUTH2 user trying to log in, already exists in the database.
 | 
				
			||||||
                                        If user exists, login proceeds as usual. If user does not exist, then it is autocreated but only if 'OAUTH2AutoCreateUser'
 | 
					                                                If user exists, login proceeds as usual. If user does not exist, then it is autocreated but only if 'OAUTH2AutoCreateUser'
 | 
				
			||||||
                                        is set as true, else login fails with an error message advising the same.
 | 
					                                                is set as true, else login fails with an error message advising the same.
 | 
				
			||||||
                                         */
 | 
					                                                 */
 | 
				
			||||||
                                        .successHandler(
 | 
					                                                .successHandler(
 | 
				
			||||||
                                                new AuthenticationSuccessHandler() {
 | 
					                                                        new CustomOAuth2AuthenticationSuccessHandler(
 | 
				
			||||||
                                                    @Override
 | 
					                                                                loginAttemptService,
 | 
				
			||||||
                                                    public void onAuthenticationSuccess(
 | 
					                                                                applicationProperties,
 | 
				
			||||||
                                                            HttpServletRequest request,
 | 
					                                                                userService))
 | 
				
			||||||
                                                            HttpServletResponse response,
 | 
					                                                .failureHandler(
 | 
				
			||||||
                                                            Authentication authentication)
 | 
					                                                        new CustomOAuth2AuthenticationFailureHandler())
 | 
				
			||||||
                                                            throws ServletException, IOException {
 | 
					                                                // Add existing Authorities from the database
 | 
				
			||||||
                                                        OAuth2User oauthUser =
 | 
					                                                .userInfoEndpoint(
 | 
				
			||||||
                                                                (OAuth2User)
 | 
					                                                        userInfoEndpoint ->
 | 
				
			||||||
                                                                        authentication
 | 
					                                                                userInfoEndpoint
 | 
				
			||||||
                                                                                .getPrincipal();
 | 
					                                                                        .oidcUserService(
 | 
				
			||||||
                                                        if (userService.processOAuth2PostLogin(
 | 
					                                                                                new CustomOAuth2UserService(
 | 
				
			||||||
                                                                oauthUser.getAttribute("email"),
 | 
					                                                                                        applicationProperties,
 | 
				
			||||||
                                                                applicationProperties
 | 
					                                                                                        userService,
 | 
				
			||||||
                                                                        .getSecurity()
 | 
					                                                                                        loginAttemptService))
 | 
				
			||||||
                                                                        .getOAUTH2()
 | 
					                                                                        .userAuthoritiesMapper(
 | 
				
			||||||
                                                                        .getAutoCreateUser())) {
 | 
					                                                                                userAuthoritiesMapper())))
 | 
				
			||||||
                                                            response.sendRedirect("/");
 | 
					                        .logout(
 | 
				
			||||||
                                                        } else {
 | 
					                                logout ->
 | 
				
			||||||
                                                            response.sendRedirect(
 | 
					                                        logout.logoutSuccessHandler(
 | 
				
			||||||
                                                                    "/logout?oauth2AutoCreateDisabled=true");
 | 
					                                                new CustomOAuth2LogoutSuccessHandler(
 | 
				
			||||||
                                                        }
 | 
					                                                        this.applicationProperties,
 | 
				
			||||||
                                                    }
 | 
					                                                        sessionRegistry())));
 | 
				
			||||||
                                                })
 | 
					 | 
				
			||||||
                                        // Add existing Authorities from the database
 | 
					 | 
				
			||||||
                                        .userInfoEndpoint(
 | 
					 | 
				
			||||||
                                                userInfoEndpoint ->
 | 
					 | 
				
			||||||
                                                        userInfoEndpoint.userAuthoritiesMapper(
 | 
					 | 
				
			||||||
                                                                userAuthoritiesMapper())));
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            http.csrf(csrf -> csrf.disable())
 | 
					            http.csrf(csrf -> csrf.disable())
 | 
				
			||||||
@ -225,13 +204,13 @@ public class SecurityConfiguration {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private ClientRegistration oidcClientRegistration() {
 | 
					    private ClientRegistration oidcClientRegistration() {
 | 
				
			||||||
        return ClientRegistrations.fromOidcIssuerLocation(
 | 
					        OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
 | 
				
			||||||
                        applicationProperties.getSecurity().getOAUTH2().getIssuer())
 | 
					        return ClientRegistrations.fromIssuerLocation(oauth.getIssuer())
 | 
				
			||||||
                .registrationId("oidc")
 | 
					                .registrationId("oidc")
 | 
				
			||||||
                .clientId(applicationProperties.getSecurity().getOAUTH2().getClientId())
 | 
					                .clientId(oauth.getClientId())
 | 
				
			||||||
                .clientSecret(applicationProperties.getSecurity().getOAUTH2().getClientSecret())
 | 
					                .clientSecret(oauth.getClientSecret())
 | 
				
			||||||
                .scope("openid", "profile", "email")
 | 
					                .scope(oauth.getScopes())
 | 
				
			||||||
                .userNameAttributeName("email")
 | 
					                .userNameAttributeName(oauth.getUseAsUsername())
 | 
				
			||||||
                .clientName("OIDC")
 | 
					                .clientName("OIDC")
 | 
				
			||||||
                .build();
 | 
					                .build();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -256,9 +235,14 @@ public class SecurityConfiguration {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                        // Add Authorities from database for existing user, if user is present.
 | 
					                        // Add Authorities from database for existing user, if user is present.
 | 
				
			||||||
                        if (authority instanceof OAuth2UserAuthority oauth2Auth) {
 | 
					                        if (authority instanceof OAuth2UserAuthority oauth2Auth) {
 | 
				
			||||||
 | 
					                            String useAsUsername =
 | 
				
			||||||
 | 
					                                    applicationProperties
 | 
				
			||||||
 | 
					                                            .getSecurity()
 | 
				
			||||||
 | 
					                                            .getOAUTH2()
 | 
				
			||||||
 | 
					                                            .getUseAsUsername();
 | 
				
			||||||
                            Optional<User> userOpt =
 | 
					                            Optional<User> userOpt =
 | 
				
			||||||
                                    userService.findByUsernameIgnoreCase(
 | 
					                                    userService.findByUsernameIgnoreCase(
 | 
				
			||||||
                                            (String) oauth2Auth.getAttributes().get("email"));
 | 
					                                            (String) oauth2Auth.getAttributes().get(useAsUsername));
 | 
				
			||||||
                            if (userOpt.isPresent()) {
 | 
					                            if (userOpt.isPresent()) {
 | 
				
			||||||
                                User user = userOpt.get();
 | 
					                                User user = userOpt.get();
 | 
				
			||||||
                                if (user != null) {
 | 
					                                if (user != null) {
 | 
				
			||||||
 | 
				
			|||||||
@ -8,6 +8,8 @@ import java.util.UUID;
 | 
				
			|||||||
import java.util.stream.Collectors;
 | 
					import java.util.stream.Collectors;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
					import org.springframework.beans.factory.annotation.Autowired;
 | 
				
			||||||
 | 
					import org.springframework.context.MessageSource;
 | 
				
			||||||
 | 
					import org.springframework.context.i18n.LocaleContextHolder;
 | 
				
			||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 | 
					import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 | 
				
			||||||
import org.springframework.security.core.Authentication;
 | 
					import org.springframework.security.core.Authentication;
 | 
				
			||||||
import org.springframework.security.core.GrantedAuthority;
 | 
					import org.springframework.security.core.GrantedAuthority;
 | 
				
			||||||
@ -18,6 +20,7 @@ import org.springframework.security.crypto.password.PasswordEncoder;
 | 
				
			|||||||
import org.springframework.stereotype.Service;
 | 
					import org.springframework.stereotype.Service;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
 | 
					import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
 | 
				
			||||||
 | 
					import stirling.software.SPDF.model.AuthenticationType;
 | 
				
			||||||
import stirling.software.SPDF.model.Authority;
 | 
					import stirling.software.SPDF.model.Authority;
 | 
				
			||||||
import stirling.software.SPDF.model.Role;
 | 
					import stirling.software.SPDF.model.Role;
 | 
				
			||||||
import stirling.software.SPDF.model.User;
 | 
					import stirling.software.SPDF.model.User;
 | 
				
			||||||
@ -33,19 +36,19 @@ public class UserService implements UserServiceInterface {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @Autowired private PasswordEncoder passwordEncoder;
 | 
					    @Autowired private PasswordEncoder passwordEncoder;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Autowired private MessageSource messageSource;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Handle OAUTH2 login and user auto creation.
 | 
					    // Handle OAUTH2 login and user auto creation.
 | 
				
			||||||
    public boolean processOAuth2PostLogin(String username, boolean autoCreateUser) {
 | 
					    public boolean processOAuth2PostLogin(String username, boolean autoCreateUser) {
 | 
				
			||||||
        Optional<User> existUser = userRepository.findByUsernameIgnoreCase(username);
 | 
					        if (!isUsernameValid(username)) {
 | 
				
			||||||
        if (existUser.isPresent()) {
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Optional<User> existingUser = userRepository.findByUsernameIgnoreCase(username);
 | 
				
			||||||
 | 
					        if (existingUser.isPresent()) {
 | 
				
			||||||
            return true;
 | 
					            return true;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (autoCreateUser) {
 | 
					        if (autoCreateUser) {
 | 
				
			||||||
            User user = new User();
 | 
					            saveUser(username, AuthenticationType.OAUTH2);
 | 
				
			||||||
            user.setUsername(username);
 | 
					 | 
				
			||||||
            user.setEnabled(true);
 | 
					 | 
				
			||||||
            user.setFirstLogin(false);
 | 
					 | 
				
			||||||
            user.addAuthority(new Authority(Role.USER.getRoleId(), user));
 | 
					 | 
				
			||||||
            userRepository.save(user);
 | 
					 | 
				
			||||||
            return true;
 | 
					            return true;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return false;
 | 
					        return false;
 | 
				
			||||||
@ -111,9 +114,8 @@ public class UserService implements UserServiceInterface {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public UserDetails loadUserByApiKey(String apiKey) {
 | 
					    public UserDetails loadUserByApiKey(String apiKey) {
 | 
				
			||||||
        User userOptional = userRepository.findByApiKey(apiKey);
 | 
					        User user = userRepository.findByApiKey(apiKey);
 | 
				
			||||||
        if (userOptional != null) {
 | 
					        if (user != null) {
 | 
				
			||||||
            User user = userOptional;
 | 
					 | 
				
			||||||
            // Convert your User entity to a UserDetails object with authorities
 | 
					            // Convert your User entity to a UserDetails object with authorities
 | 
				
			||||||
            return new org.springframework.security.core.userdetails.User(
 | 
					            return new org.springframework.security.core.userdetails.User(
 | 
				
			||||||
                    user.getUsername(),
 | 
					                    user.getUsername(),
 | 
				
			||||||
@ -125,35 +127,53 @@ public class UserService implements UserServiceInterface {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    public boolean validateApiKeyForUser(String username, String apiKey) {
 | 
					    public boolean validateApiKeyForUser(String username, String apiKey) {
 | 
				
			||||||
        Optional<User> userOpt = userRepository.findByUsernameIgnoreCase(username);
 | 
					        Optional<User> userOpt = userRepository.findByUsernameIgnoreCase(username);
 | 
				
			||||||
        return userOpt.isPresent() && userOpt.get().getApiKey().equals(apiKey);
 | 
					        return userOpt.isPresent() && apiKey.equals(userOpt.get().getApiKey());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public void saveUser(String username, String password) {
 | 
					    public void saveUser(String username, AuthenticationType authenticationType)
 | 
				
			||||||
 | 
					            throws IllegalArgumentException {
 | 
				
			||||||
 | 
					        if (!isUsernameValid(username)) {
 | 
				
			||||||
 | 
					            throw new IllegalArgumentException(getInvalidUsernameMessage());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        User user = new User();
 | 
				
			||||||
 | 
					        user.setUsername(username);
 | 
				
			||||||
 | 
					        user.setEnabled(true);
 | 
				
			||||||
 | 
					        user.setFirstLogin(false);
 | 
				
			||||||
 | 
					        user.addAuthority(new Authority(Role.USER.getRoleId(), user));
 | 
				
			||||||
 | 
					        user.setAuthenticationType(authenticationType);
 | 
				
			||||||
 | 
					        userRepository.save(user);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public void saveUser(String username, String password) throws IllegalArgumentException {
 | 
				
			||||||
 | 
					        if (!isUsernameValid(username)) {
 | 
				
			||||||
 | 
					            throw new IllegalArgumentException(getInvalidUsernameMessage());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        User user = new User();
 | 
					        User user = new User();
 | 
				
			||||||
        user.setUsername(username);
 | 
					        user.setUsername(username);
 | 
				
			||||||
        user.setPassword(passwordEncoder.encode(password));
 | 
					        user.setPassword(passwordEncoder.encode(password));
 | 
				
			||||||
        user.setEnabled(true);
 | 
					        user.setEnabled(true);
 | 
				
			||||||
 | 
					        user.setAuthenticationType(AuthenticationType.WEB);
 | 
				
			||||||
        userRepository.save(user);
 | 
					        userRepository.save(user);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public void saveUser(String username, String password, String role, boolean firstLogin) {
 | 
					    public void saveUser(String username, String password, String role, boolean firstLogin)
 | 
				
			||||||
 | 
					            throws IllegalArgumentException {
 | 
				
			||||||
 | 
					        if (!isUsernameValid(username)) {
 | 
				
			||||||
 | 
					            throw new IllegalArgumentException(getInvalidUsernameMessage());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        User user = new User();
 | 
					        User user = new User();
 | 
				
			||||||
        user.setUsername(username);
 | 
					        user.setUsername(username);
 | 
				
			||||||
        user.setPassword(passwordEncoder.encode(password));
 | 
					        user.setPassword(passwordEncoder.encode(password));
 | 
				
			||||||
        user.addAuthority(new Authority(role, user));
 | 
					        user.addAuthority(new Authority(role, user));
 | 
				
			||||||
        user.setEnabled(true);
 | 
					        user.setEnabled(true);
 | 
				
			||||||
 | 
					        user.setAuthenticationType(AuthenticationType.WEB);
 | 
				
			||||||
        user.setFirstLogin(firstLogin);
 | 
					        user.setFirstLogin(firstLogin);
 | 
				
			||||||
        userRepository.save(user);
 | 
					        userRepository.save(user);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public void saveUser(String username, String password, String role) {
 | 
					    public void saveUser(String username, String password, String role)
 | 
				
			||||||
        User user = new User();
 | 
					            throws IllegalArgumentException {
 | 
				
			||||||
        user.setUsername(username);
 | 
					        saveUser(username, password, role, false);
 | 
				
			||||||
        user.setPassword(passwordEncoder.encode(password));
 | 
					 | 
				
			||||||
        user.addAuthority(new Authority(role, user));
 | 
					 | 
				
			||||||
        user.setEnabled(true);
 | 
					 | 
				
			||||||
        user.setFirstLogin(false);
 | 
					 | 
				
			||||||
        userRepository.save(user);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public void deleteUser(String username) {
 | 
					    public void deleteUser(String username) {
 | 
				
			||||||
@ -187,7 +207,7 @@ public class UserService implements UserServiceInterface {
 | 
				
			|||||||
            Map<String, String> settingsMap = user.getSettings();
 | 
					            Map<String, String> settingsMap = user.getSettings();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (settingsMap == null) {
 | 
					            if (settingsMap == null) {
 | 
				
			||||||
                settingsMap = new HashMap<String, String>();
 | 
					                settingsMap = new HashMap<>();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            settingsMap.clear();
 | 
					            settingsMap.clear();
 | 
				
			||||||
            settingsMap.putAll(updates);
 | 
					            settingsMap.putAll(updates);
 | 
				
			||||||
@ -209,7 +229,10 @@ public class UserService implements UserServiceInterface {
 | 
				
			|||||||
        return authorityRepository.findByUserId(user.getId());
 | 
					        return authorityRepository.findByUserId(user.getId());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public void changeUsername(User user, String newUsername) {
 | 
					    public void changeUsername(User user, String newUsername) throws IllegalArgumentException {
 | 
				
			||||||
 | 
					        if (!isUsernameValid(newUsername)) {
 | 
				
			||||||
 | 
					            throw new IllegalArgumentException(getInvalidUsernameMessage());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        user.setUsername(newUsername);
 | 
					        user.setUsername(newUsername);
 | 
				
			||||||
        userRepository.save(user);
 | 
					        userRepository.save(user);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -235,6 +258,30 @@ public class UserService implements UserServiceInterface {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public boolean isUsernameValid(String username) {
 | 
					    public boolean isUsernameValid(String username) {
 | 
				
			||||||
        return username.matches("[a-zA-Z0-9]+");
 | 
					        // Checks whether the simple username is formatted correctly
 | 
				
			||||||
 | 
					        boolean isValidSimpleUsername =
 | 
				
			||||||
 | 
					                username.matches("^[a-zA-Z0-9][a-zA-Z0-9@._+-]*[a-zA-Z0-9]$");
 | 
				
			||||||
 | 
					        // Checks whether the email address is formatted correctly
 | 
				
			||||||
 | 
					        boolean isValidEmail =
 | 
				
			||||||
 | 
					                username.matches(
 | 
				
			||||||
 | 
					                        "^(?=.{1,64}@)[A-Za-z0-9]+(\\.[A-Za-z0-9_+.-]+)*@[^-][A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(\\.[A-Za-z]{2,})$");
 | 
				
			||||||
 | 
					        return isValidSimpleUsername || isValidEmail;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private String getInvalidUsernameMessage() {
 | 
				
			||||||
 | 
					        return messageSource.getMessage(
 | 
				
			||||||
 | 
					                "invalidUsernameMessage", null, LocaleContextHolder.getLocale());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public boolean hasPassword(String username) {
 | 
				
			||||||
 | 
					        Optional<User> user = userRepository.findByUsernameIgnoreCase(username);
 | 
				
			||||||
 | 
					        return user.isPresent() && user.get().hasPassword();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public boolean isAuthenticationTypeByUsername(
 | 
				
			||||||
 | 
					            String username, AuthenticationType authenticationType) {
 | 
				
			||||||
 | 
					        Optional<User> user = userRepository.findByUsernameIgnoreCase(username);
 | 
				
			||||||
 | 
					        return user.isPresent()
 | 
				
			||||||
 | 
					                && authenticationType.name().equalsIgnoreCase(user.get().getAuthenticationType());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,49 @@
 | 
				
			|||||||
 | 
					package stirling.software.SPDF.config.security.oauth2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.io.IOException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.slf4j.Logger;
 | 
				
			||||||
 | 
					import org.slf4j.LoggerFactory;
 | 
				
			||||||
 | 
					import org.springframework.security.authentication.LockedException;
 | 
				
			||||||
 | 
					import org.springframework.security.core.AuthenticationException;
 | 
				
			||||||
 | 
					import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 | 
				
			||||||
 | 
					import org.springframework.security.oauth2.core.OAuth2Error;
 | 
				
			||||||
 | 
					import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import jakarta.servlet.ServletException;
 | 
				
			||||||
 | 
					import jakarta.servlet.http.HttpServletRequest;
 | 
				
			||||||
 | 
					import jakarta.servlet.http.HttpServletResponse;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class CustomOAuth2AuthenticationFailureHandler
 | 
				
			||||||
 | 
					        extends SimpleUrlAuthenticationFailureHandler {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static final Logger logger =
 | 
				
			||||||
 | 
					            LoggerFactory.getLogger(CustomOAuth2AuthenticationFailureHandler.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public void onAuthenticationFailure(
 | 
				
			||||||
 | 
					            HttpServletRequest request,
 | 
				
			||||||
 | 
					            HttpServletResponse response,
 | 
				
			||||||
 | 
					            AuthenticationException exception)
 | 
				
			||||||
 | 
					            throws IOException, ServletException {
 | 
				
			||||||
 | 
					        if (exception instanceof OAuth2AuthenticationException) {
 | 
				
			||||||
 | 
					            OAuth2Error error = ((OAuth2AuthenticationException) exception).getError();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            String errorCode = error.getErrorCode();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (error.getErrorCode().equals("Password must not be null")) {
 | 
				
			||||||
 | 
					                errorCode = "userAlreadyExistsWeb";
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            logger.error("OAuth2 Authentication error: " + errorCode);
 | 
				
			||||||
 | 
					            getRedirectStrategy()
 | 
				
			||||||
 | 
					                    .sendRedirect(request, response, "/logout?erroroauth=" + errorCode);
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        } else if (exception instanceof LockedException) {
 | 
				
			||||||
 | 
					            logger.error("Account locked: ", exception);
 | 
				
			||||||
 | 
					            getRedirectStrategy().sendRedirect(request, response, "/logout?error=locked");
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            logger.error("Unhandled authentication exception", exception);
 | 
				
			||||||
 | 
					            super.onAuthenticationFailure(request, response, exception);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,93 @@
 | 
				
			|||||||
 | 
					package stirling.software.SPDF.config.security.oauth2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.io.IOException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.slf4j.Logger;
 | 
				
			||||||
 | 
					import org.slf4j.LoggerFactory;
 | 
				
			||||||
 | 
					import org.springframework.security.authentication.LockedException;
 | 
				
			||||||
 | 
					import org.springframework.security.core.Authentication;
 | 
				
			||||||
 | 
					import org.springframework.security.oauth2.core.user.OAuth2User;
 | 
				
			||||||
 | 
					import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
 | 
				
			||||||
 | 
					import org.springframework.security.web.savedrequest.SavedRequest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import jakarta.servlet.ServletException;
 | 
				
			||||||
 | 
					import jakarta.servlet.http.HttpServletRequest;
 | 
				
			||||||
 | 
					import jakarta.servlet.http.HttpServletResponse;
 | 
				
			||||||
 | 
					import jakarta.servlet.http.HttpSession;
 | 
				
			||||||
 | 
					import stirling.software.SPDF.config.security.LoginAttemptService;
 | 
				
			||||||
 | 
					import stirling.software.SPDF.config.security.UserService;
 | 
				
			||||||
 | 
					import stirling.software.SPDF.model.ApplicationProperties;
 | 
				
			||||||
 | 
					import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
 | 
				
			||||||
 | 
					import stirling.software.SPDF.model.AuthenticationType;
 | 
				
			||||||
 | 
					import stirling.software.SPDF.utils.RequestUriUtils;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class CustomOAuth2AuthenticationSuccessHandler
 | 
				
			||||||
 | 
					        extends SavedRequestAwareAuthenticationSuccessHandler {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private LoginAttemptService loginAttemptService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static final Logger logger =
 | 
				
			||||||
 | 
					            LoggerFactory.getLogger(CustomOAuth2AuthenticationSuccessHandler.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private ApplicationProperties applicationProperties;
 | 
				
			||||||
 | 
					    private UserService userService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public CustomOAuth2AuthenticationSuccessHandler(
 | 
				
			||||||
 | 
					            final LoginAttemptService loginAttemptService,
 | 
				
			||||||
 | 
					            ApplicationProperties applicationProperties,
 | 
				
			||||||
 | 
					            UserService userService) {
 | 
				
			||||||
 | 
					        this.applicationProperties = applicationProperties;
 | 
				
			||||||
 | 
					        this.userService = userService;
 | 
				
			||||||
 | 
					        this.loginAttemptService = loginAttemptService;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public void onAuthenticationSuccess(
 | 
				
			||||||
 | 
					            HttpServletRequest request, HttpServletResponse response, Authentication authentication)
 | 
				
			||||||
 | 
					            throws ServletException, IOException {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Get the saved request
 | 
				
			||||||
 | 
					        HttpSession session = request.getSession(false);
 | 
				
			||||||
 | 
					        SavedRequest savedRequest =
 | 
				
			||||||
 | 
					                (session != null)
 | 
				
			||||||
 | 
					                        ? (SavedRequest) session.getAttribute("SPRING_SECURITY_SAVED_REQUEST")
 | 
				
			||||||
 | 
					                        : null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (savedRequest != null
 | 
				
			||||||
 | 
					                && !RequestUriUtils.isStaticResource(savedRequest.getRedirectUrl())) {
 | 
				
			||||||
 | 
					            // Redirect to the original destination
 | 
				
			||||||
 | 
					            super.onAuthenticationSuccess(request, response, authentication);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            OAuth2User oauthUser = (OAuth2User) authentication.getPrincipal();
 | 
				
			||||||
 | 
					            OAUTH2 oAuth = applicationProperties.getSecurity().getOAUTH2();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            String username = oauthUser.getName();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (loginAttemptService.isBlocked(username)) {
 | 
				
			||||||
 | 
					                if (session != null) {
 | 
				
			||||||
 | 
					                    session.removeAttribute("SPRING_SECURITY_SAVED_REQUEST");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                throw new LockedException(
 | 
				
			||||||
 | 
					                        "Your account has been locked due to too many failed login attempts.");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (userService.usernameExistsIgnoreCase(username)
 | 
				
			||||||
 | 
					                    && userService.hasPassword(username)
 | 
				
			||||||
 | 
					                    && !userService.isAuthenticationTypeByUsername(
 | 
				
			||||||
 | 
					                            username, AuthenticationType.OAUTH2)
 | 
				
			||||||
 | 
					                    && oAuth.getAutoCreateUser()) {
 | 
				
			||||||
 | 
					                response.sendRedirect(
 | 
				
			||||||
 | 
					                        request.getContextPath() + "/logout?oauth2AuthenticationErrorWeb=true");
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                try {
 | 
				
			||||||
 | 
					                    userService.processOAuth2PostLogin(username, oAuth.getAutoCreateUser());
 | 
				
			||||||
 | 
					                    response.sendRedirect("/");
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                } catch (IllegalArgumentException e) {
 | 
				
			||||||
 | 
					                    response.sendRedirect("/logout?invalidUsername=true");
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,86 @@
 | 
				
			|||||||
 | 
					package stirling.software.SPDF.config.security.oauth2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.io.IOException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.slf4j.Logger;
 | 
				
			||||||
 | 
					import org.slf4j.LoggerFactory;
 | 
				
			||||||
 | 
					import org.springframework.security.core.Authentication;
 | 
				
			||||||
 | 
					import org.springframework.security.core.session.SessionRegistry;
 | 
				
			||||||
 | 
					import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import jakarta.servlet.ServletException;
 | 
				
			||||||
 | 
					import jakarta.servlet.http.HttpServletRequest;
 | 
				
			||||||
 | 
					import jakarta.servlet.http.HttpServletResponse;
 | 
				
			||||||
 | 
					import jakarta.servlet.http.HttpSession;
 | 
				
			||||||
 | 
					import stirling.software.SPDF.model.ApplicationProperties;
 | 
				
			||||||
 | 
					import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class CustomOAuth2LogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static final Logger logger =
 | 
				
			||||||
 | 
					            LoggerFactory.getLogger(CustomOAuth2LogoutSuccessHandler.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private final SessionRegistry sessionRegistry;
 | 
				
			||||||
 | 
					    private final ApplicationProperties applicationProperties;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public CustomOAuth2LogoutSuccessHandler(
 | 
				
			||||||
 | 
					            ApplicationProperties applicationProperties, SessionRegistry sessionRegistry) {
 | 
				
			||||||
 | 
					        this.sessionRegistry = sessionRegistry;
 | 
				
			||||||
 | 
					        this.applicationProperties = applicationProperties;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public void onLogoutSuccess(
 | 
				
			||||||
 | 
					            HttpServletRequest request, HttpServletResponse response, Authentication authentication)
 | 
				
			||||||
 | 
					            throws IOException, ServletException {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        String param = "logout=true";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
 | 
				
			||||||
 | 
					        String provider = oauth.getProvider() != null ? oauth.getProvider() : "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (request.getParameter("oauth2AuthenticationErrorWeb") != null) {
 | 
				
			||||||
 | 
					            param = "erroroauth=oauth2AuthenticationErrorWeb";
 | 
				
			||||||
 | 
					        } else if (request.getParameter("error") != null) {
 | 
				
			||||||
 | 
					            param = "error=" + request.getParameter("error");
 | 
				
			||||||
 | 
					        } else if (request.getParameter("erroroauth") != null) {
 | 
				
			||||||
 | 
					            param = "erroroauth=" + request.getParameter("erroroauth");
 | 
				
			||||||
 | 
					        } else if (request.getParameter("oauth2AutoCreateDisabled") != null) {
 | 
				
			||||||
 | 
					            param = "error=oauth2AutoCreateDisabled";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        HttpSession session = request.getSession(false);
 | 
				
			||||||
 | 
					        if (session != null) {
 | 
				
			||||||
 | 
					            String sessionId = session.getId();
 | 
				
			||||||
 | 
					            sessionRegistry.removeSessionInformation(sessionId);
 | 
				
			||||||
 | 
					            session.invalidate();
 | 
				
			||||||
 | 
					            logger.debug("Session invalidated: " + sessionId);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        switch (provider) {
 | 
				
			||||||
 | 
					            case "keycloak":
 | 
				
			||||||
 | 
					                String logoutUrl =
 | 
				
			||||||
 | 
					                        oauth.getIssuer()
 | 
				
			||||||
 | 
					                                + "/protocol/openid-connect/logout"
 | 
				
			||||||
 | 
					                                + "?client_id="
 | 
				
			||||||
 | 
					                                + oauth.getClientId()
 | 
				
			||||||
 | 
					                                + "&post_logout_redirect_uri="
 | 
				
			||||||
 | 
					                                + response.encodeRedirectURL(
 | 
				
			||||||
 | 
					                                        request.getScheme()
 | 
				
			||||||
 | 
					                                                + "://"
 | 
				
			||||||
 | 
					                                                + request.getHeader("host")
 | 
				
			||||||
 | 
					                                                + "/login?"
 | 
				
			||||||
 | 
					                                                + param);
 | 
				
			||||||
 | 
					                logger.debug("Redirecting to Keycloak logout URL: " + logoutUrl);
 | 
				
			||||||
 | 
					                response.sendRedirect(logoutUrl);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            case "google":
 | 
				
			||||||
 | 
					                // Add Google specific logout URL if needed
 | 
				
			||||||
 | 
					            default:
 | 
				
			||||||
 | 
					                String redirectUrl = request.getContextPath() + "/login?" + param;
 | 
				
			||||||
 | 
					                logger.debug("Redirecting to default logout URL: " + redirectUrl);
 | 
				
			||||||
 | 
					                response.sendRedirect(redirectUrl);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,73 @@
 | 
				
			|||||||
 | 
					package stirling.software.SPDF.config.security.oauth2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.Optional;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.slf4j.Logger;
 | 
				
			||||||
 | 
					import org.slf4j.LoggerFactory;
 | 
				
			||||||
 | 
					import org.springframework.security.authentication.LockedException;
 | 
				
			||||||
 | 
					import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
 | 
				
			||||||
 | 
					import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
 | 
				
			||||||
 | 
					import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
 | 
				
			||||||
 | 
					import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 | 
				
			||||||
 | 
					import org.springframework.security.oauth2.core.OAuth2Error;
 | 
				
			||||||
 | 
					import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
 | 
				
			||||||
 | 
					import org.springframework.security.oauth2.core.oidc.user.OidcUser;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import stirling.software.SPDF.config.security.LoginAttemptService;
 | 
				
			||||||
 | 
					import stirling.software.SPDF.config.security.UserService;
 | 
				
			||||||
 | 
					import stirling.software.SPDF.model.ApplicationProperties;
 | 
				
			||||||
 | 
					import stirling.software.SPDF.model.User;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class CustomOAuth2UserService implements OAuth2UserService<OidcUserRequest, OidcUser> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private final OidcUserService delegate = new OidcUserService();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private UserService userService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private LoginAttemptService loginAttemptService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private ApplicationProperties applicationProperties;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static final Logger logger = LoggerFactory.getLogger(CustomOAuth2UserService.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public CustomOAuth2UserService(
 | 
				
			||||||
 | 
					            ApplicationProperties applicationProperties,
 | 
				
			||||||
 | 
					            UserService userService,
 | 
				
			||||||
 | 
					            LoginAttemptService loginAttemptService) {
 | 
				
			||||||
 | 
					        this.applicationProperties = applicationProperties;
 | 
				
			||||||
 | 
					        this.userService = userService;
 | 
				
			||||||
 | 
					        this.loginAttemptService = loginAttemptService;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
 | 
				
			||||||
 | 
					        String usernameAttribute =
 | 
				
			||||||
 | 
					                applicationProperties.getSecurity().getOAUTH2().getUseAsUsername();
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            OidcUser user = delegate.loadUser(userRequest);
 | 
				
			||||||
 | 
					            String username = user.getUserInfo().getClaimAsString(usernameAttribute);
 | 
				
			||||||
 | 
					            Optional<User> duser = userService.findByUsernameIgnoreCase(username);
 | 
				
			||||||
 | 
					            if (duser.isPresent()) {
 | 
				
			||||||
 | 
					                if (loginAttemptService.isBlocked(username)) {
 | 
				
			||||||
 | 
					                    throw new LockedException(
 | 
				
			||||||
 | 
					                            "Your account has been locked due to too many failed login attempts.");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (userService.hasPassword(username)) {
 | 
				
			||||||
 | 
					                    throw new IllegalArgumentException("Password must not be null");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            // Return a new OidcUser with adjusted attributes
 | 
				
			||||||
 | 
					            return new DefaultOidcUser(
 | 
				
			||||||
 | 
					                    user.getAuthorities(),
 | 
				
			||||||
 | 
					                    userRequest.getIdToken(),
 | 
				
			||||||
 | 
					                    user.getUserInfo(),
 | 
				
			||||||
 | 
					                    usernameAttribute);
 | 
				
			||||||
 | 
					        } catch (java.lang.IllegalArgumentException e) {
 | 
				
			||||||
 | 
					            logger.error("Error loading OIDC user: {}", e.getMessage());
 | 
				
			||||||
 | 
					            throw new OAuth2AuthenticationException(new OAuth2Error(e.getMessage()), e);
 | 
				
			||||||
 | 
					        } catch (Exception e) {
 | 
				
			||||||
 | 
					            logger.error("Unexpected error loading OIDC user", e);
 | 
				
			||||||
 | 
					            throw new OAuth2AuthenticationException("Unexpected error during authentication");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,57 @@
 | 
				
			|||||||
 | 
					package stirling.software.SPDF.config.security.oauth2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.HashMap;
 | 
				
			||||||
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.slf4j.Logger;
 | 
				
			||||||
 | 
					import org.slf4j.LoggerFactory;
 | 
				
			||||||
 | 
					import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
 | 
				
			||||||
 | 
					import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
 | 
				
			||||||
 | 
					import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
 | 
				
			||||||
 | 
					import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 | 
				
			||||||
 | 
					import org.springframework.security.oauth2.core.OAuth2Error;
 | 
				
			||||||
 | 
					import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
 | 
				
			||||||
 | 
					import org.springframework.security.oauth2.core.oidc.user.OidcUser;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import stirling.software.SPDF.model.ApplicationProperties;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class CustomOAuthUserService implements OAuth2UserService<OidcUserRequest, OidcUser> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static final Logger logger = LoggerFactory.getLogger(CustomOAuthUserService.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private final OidcUserService delegate = new OidcUserService();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private ApplicationProperties applicationProperties;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public CustomOAuthUserService(ApplicationProperties applicationProperties) {
 | 
				
			||||||
 | 
					        this.applicationProperties = applicationProperties;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
 | 
				
			||||||
 | 
					        String usernameAttribute =
 | 
				
			||||||
 | 
					                applicationProperties.getSecurity().getOAUTH2().getUseAsUsername();
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            OidcUser user = delegate.loadUser(userRequest);
 | 
				
			||||||
 | 
					            Map<String, Object> attributes = new HashMap<>(user.getAttributes());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Ensure the preferred username attribute is present
 | 
				
			||||||
 | 
					            if (!attributes.containsKey(usernameAttribute)) {
 | 
				
			||||||
 | 
					                attributes.put(usernameAttribute, attributes.getOrDefault("email", ""));
 | 
				
			||||||
 | 
					                usernameAttribute = "email";
 | 
				
			||||||
 | 
					                logger.info("Adjusted username attribute to use email");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Return a new OidcUser with adjusted attributes
 | 
				
			||||||
 | 
					            return new DefaultOidcUser(
 | 
				
			||||||
 | 
					                    user.getAuthorities(),
 | 
				
			||||||
 | 
					                    userRequest.getIdToken(),
 | 
				
			||||||
 | 
					                    user.getUserInfo(),
 | 
				
			||||||
 | 
					                    usernameAttribute);
 | 
				
			||||||
 | 
					        } catch (java.lang.IllegalArgumentException e) {
 | 
				
			||||||
 | 
					            throw new OAuth2AuthenticationException(
 | 
				
			||||||
 | 
					                    new OAuth2Error(e.getMessage()), e.getMessage(), e);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -47,8 +47,11 @@ public class UserController {
 | 
				
			|||||||
            model.addAttribute("error", "Username already exists");
 | 
					            model.addAttribute("error", "Username already exists");
 | 
				
			||||||
            return "register";
 | 
					            return "register";
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
        userService.saveUser(requestModel.getUsername(), requestModel.getPassword());
 | 
					            userService.saveUser(requestModel.getUsername(), requestModel.getPassword());
 | 
				
			||||||
 | 
					        } catch (IllegalArgumentException e) {
 | 
				
			||||||
 | 
					            return "redirect:/login?messageType=invalidUsername";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        return "redirect:/login?registered=true";
 | 
					        return "redirect:/login?registered=true";
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -92,7 +95,11 @@ public class UserController {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (newUsername != null && newUsername.length() > 0) {
 | 
					        if (newUsername != null && newUsername.length() > 0) {
 | 
				
			||||||
            userService.changeUsername(user, newUsername);
 | 
					            try {
 | 
				
			||||||
 | 
					                userService.changeUsername(user, newUsername);
 | 
				
			||||||
 | 
					            } catch (IllegalArgumentException e) {
 | 
				
			||||||
 | 
					                return new RedirectView("/account?messageType=invalidUsername");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Logout using Spring's utility
 | 
					        // Logout using Spring's utility
 | 
				
			||||||
 | 
				
			|||||||
@ -43,9 +43,52 @@ public class AccountWebController {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        model.addAttribute("currentPage", "login");
 | 
					        model.addAttribute("currentPage", "login");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (request.getParameter("error") != null) {
 | 
					        String error = request.getParameter("error");
 | 
				
			||||||
 | 
					        if (error != null) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            model.addAttribute("error", request.getParameter("error"));
 | 
					            switch (error) {
 | 
				
			||||||
 | 
					                case "badcredentials":
 | 
				
			||||||
 | 
					                    error = "login.invalid";
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                case "locked":
 | 
				
			||||||
 | 
					                    error = "login.locked";
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                case "oauth2AuthenticationError":
 | 
				
			||||||
 | 
					                    error = "userAlreadyExistsOAuthMessage";
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                default:
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            model.addAttribute("error", error);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        String erroroauth = request.getParameter("erroroauth");
 | 
				
			||||||
 | 
					        if (erroroauth != null) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            switch (erroroauth) {
 | 
				
			||||||
 | 
					                case "oauth2AutoCreateDisabled":
 | 
				
			||||||
 | 
					                    erroroauth = "login.oauth2AutoCreateDisabled";
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                case "invalidUsername":
 | 
				
			||||||
 | 
					                    erroroauth = "login.invalid";
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                case "userAlreadyExistsWeb":
 | 
				
			||||||
 | 
					                    erroroauth = "userAlreadyExistsWebMessage";
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                case "oauth2AuthenticationErrorWeb":
 | 
				
			||||||
 | 
					                    erroroauth = "login.oauth2InvalidUserType";
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                case "invalid_token_response":
 | 
				
			||||||
 | 
					                    erroroauth = "login.oauth2InvalidTokenResponse";
 | 
				
			||||||
 | 
					                default:
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            model.addAttribute("erroroauth", erroroauth);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (request.getParameter("messageType") != null) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            model.addAttribute("messageType", "changedCredsMessage");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (request.getParameter("logout") != null) {
 | 
					        if (request.getParameter("logout") != null) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -60,7 +103,8 @@ public class AccountWebController {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @PreAuthorize("hasRole('ROLE_ADMIN')")
 | 
					    @PreAuthorize("hasRole('ROLE_ADMIN')")
 | 
				
			||||||
    @GetMapping("/addUsers")
 | 
					    @GetMapping("/addUsers")
 | 
				
			||||||
    public String showAddUserForm(Model model, Authentication authentication) {
 | 
					    public String showAddUserForm(
 | 
				
			||||||
 | 
					            HttpServletRequest request, Model model, Authentication authentication) {
 | 
				
			||||||
        List<User> allUsers = userRepository.findAll();
 | 
					        List<User> allUsers = userRepository.findAll();
 | 
				
			||||||
        Iterator<User> iterator = allUsers.iterator();
 | 
					        Iterator<User> iterator = allUsers.iterator();
 | 
				
			||||||
        Map<String, String> roleDetails = Role.getAllRoleDetails();
 | 
					        Map<String, String> roleDetails = Role.getAllRoleDetails();
 | 
				
			||||||
@ -78,6 +122,52 @@ public class AccountWebController {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        String messageType = request.getParameter("messageType");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        String deleteMessage = null;
 | 
				
			||||||
 | 
					        if (messageType != null) {
 | 
				
			||||||
 | 
					            switch (messageType) {
 | 
				
			||||||
 | 
					                case "deleteCurrentUser":
 | 
				
			||||||
 | 
					                    deleteMessage = "deleteCurrentUserMessage";
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                case "deleteUsernameExists":
 | 
				
			||||||
 | 
					                    deleteMessage = "deleteUsernameExistsMessage";
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                default:
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            model.addAttribute("deleteMessage", deleteMessage);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            String addMessage = null;
 | 
				
			||||||
 | 
					            switch (messageType) {
 | 
				
			||||||
 | 
					                case "usernameExists":
 | 
				
			||||||
 | 
					                    addMessage = "usernameExistsMessage";
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                case "invalidUsername":
 | 
				
			||||||
 | 
					                    addMessage = "invalidUsernameMessage";
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                default:
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            model.addAttribute("addMessage", addMessage);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        String changeMessage = null;
 | 
				
			||||||
 | 
					        if (messageType != null) {
 | 
				
			||||||
 | 
					            switch (messageType) {
 | 
				
			||||||
 | 
					                case "userNotFound":
 | 
				
			||||||
 | 
					                    changeMessage = "userNotFoundMessage";
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                case "downgradeCurrentUser":
 | 
				
			||||||
 | 
					                    changeMessage = "downgradeCurrentUserMessage";
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                default:
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            model.addAttribute("changeMessage", changeMessage);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        model.addAttribute("users", allUsers);
 | 
					        model.addAttribute("users", allUsers);
 | 
				
			||||||
        model.addAttribute("currentUsername", authentication.getName());
 | 
					        model.addAttribute("currentUsername", authentication.getName());
 | 
				
			||||||
        model.addAttribute("roleDetails", roleDetails);
 | 
					        model.addAttribute("roleDetails", roleDetails);
 | 
				
			||||||
@ -109,8 +199,9 @@ public class AccountWebController {
 | 
				
			|||||||
                OAuth2User userDetails = (OAuth2User) principal;
 | 
					                OAuth2User userDetails = (OAuth2User) principal;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // Retrieve username and other attributes
 | 
					                // Retrieve username and other attributes
 | 
				
			||||||
                username = userDetails.getAttribute("email");
 | 
					                username =
 | 
				
			||||||
 | 
					                        userDetails.getAttribute(
 | 
				
			||||||
 | 
					                                applicationProperties.getSecurity().getOAUTH2().getUseAsUsername());
 | 
				
			||||||
                // Add oAuth2 Login attributes to the model
 | 
					                // Add oAuth2 Login attributes to the model
 | 
				
			||||||
                model.addAttribute("oAuth2Login", true);
 | 
					                model.addAttribute("oAuth2Login", true);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -135,6 +226,30 @@ public class AccountWebController {
 | 
				
			|||||||
                    return "redirect:/error"; // Example redirection in case of error
 | 
					                    return "redirect:/error"; // Example redirection in case of error
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                String messageType = request.getParameter("messageType");
 | 
				
			||||||
 | 
					                if (messageType != null) {
 | 
				
			||||||
 | 
					                    switch (messageType) {
 | 
				
			||||||
 | 
					                        case "notAuthenticated":
 | 
				
			||||||
 | 
					                            messageType = "notAuthenticatedMessage";
 | 
				
			||||||
 | 
					                            break;
 | 
				
			||||||
 | 
					                        case "userNotFound":
 | 
				
			||||||
 | 
					                            messageType = "userNotFoundMessage";
 | 
				
			||||||
 | 
					                            break;
 | 
				
			||||||
 | 
					                        case "incorrectPassword":
 | 
				
			||||||
 | 
					                            messageType = "incorrectPasswordMessage";
 | 
				
			||||||
 | 
					                            break;
 | 
				
			||||||
 | 
					                        case "usernameExists":
 | 
				
			||||||
 | 
					                            messageType = "usernameExistsMessage";
 | 
				
			||||||
 | 
					                            break;
 | 
				
			||||||
 | 
					                        case "invalidUsername":
 | 
				
			||||||
 | 
					                            messageType = "invalidUsernameMessage";
 | 
				
			||||||
 | 
					                            break;
 | 
				
			||||||
 | 
					                        default:
 | 
				
			||||||
 | 
					                            break;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    model.addAttribute("messageType", messageType);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // Add attributes to the model
 | 
					                // Add attributes to the model
 | 
				
			||||||
                model.addAttribute("username", username);
 | 
					                model.addAttribute("username", username);
 | 
				
			||||||
                model.addAttribute("role", user.get().getRolesAsString());
 | 
					                model.addAttribute("role", user.get().getRolesAsString());
 | 
				
			||||||
@ -173,6 +288,28 @@ public class AccountWebController {
 | 
				
			|||||||
                    // Handle error appropriately
 | 
					                    // Handle error appropriately
 | 
				
			||||||
                    return "redirect:/error"; // Example redirection in case of error
 | 
					                    return "redirect:/error"; // Example redirection in case of error
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                String messageType = request.getParameter("messageType");
 | 
				
			||||||
 | 
					                if (messageType != null) {
 | 
				
			||||||
 | 
					                    switch (messageType) {
 | 
				
			||||||
 | 
					                        case "notAuthenticated":
 | 
				
			||||||
 | 
					                            messageType = "notAuthenticatedMessage";
 | 
				
			||||||
 | 
					                            break;
 | 
				
			||||||
 | 
					                        case "userNotFound":
 | 
				
			||||||
 | 
					                            messageType = "userNotFoundMessage";
 | 
				
			||||||
 | 
					                            break;
 | 
				
			||||||
 | 
					                        case "incorrectPassword":
 | 
				
			||||||
 | 
					                            messageType = "incorrectPasswordMessage";
 | 
				
			||||||
 | 
					                            break;
 | 
				
			||||||
 | 
					                        case "usernameExists":
 | 
				
			||||||
 | 
					                            messageType = "usernameExistsMessage";
 | 
				
			||||||
 | 
					                            break;
 | 
				
			||||||
 | 
					                        default:
 | 
				
			||||||
 | 
					                            break;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    model.addAttribute("messageType", messageType);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // Add attributes to the model
 | 
					                // Add attributes to the model
 | 
				
			||||||
                model.addAttribute("username", username);
 | 
					                model.addAttribute("username", username);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,10 @@
 | 
				
			|||||||
package stirling.software.SPDF.model;
 | 
					package stirling.software.SPDF.model;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.ArrayList;
 | 
				
			||||||
 | 
					import java.util.Arrays;
 | 
				
			||||||
 | 
					import java.util.Collection;
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					import java.util.stream.Collectors;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
 | 
					import org.springframework.boot.context.properties.ConfigurationProperties;
 | 
				
			||||||
import org.springframework.context.annotation.Configuration;
 | 
					import org.springframework.context.annotation.Configuration;
 | 
				
			||||||
@ -221,6 +225,10 @@ public class ApplicationProperties {
 | 
				
			|||||||
            private String clientId;
 | 
					            private String clientId;
 | 
				
			||||||
            private String clientSecret;
 | 
					            private String clientSecret;
 | 
				
			||||||
            private boolean autoCreateUser;
 | 
					            private boolean autoCreateUser;
 | 
				
			||||||
 | 
					            private String useAsUsername;
 | 
				
			||||||
 | 
					            private String provider;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            private Collection<String> scopes = new ArrayList<String>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            public boolean getEnabled() {
 | 
					            public boolean getEnabled() {
 | 
				
			||||||
                return enabled;
 | 
					                return enabled;
 | 
				
			||||||
@ -262,6 +270,37 @@ public class ApplicationProperties {
 | 
				
			|||||||
                this.autoCreateUser = autoCreateUser;
 | 
					                this.autoCreateUser = autoCreateUser;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            public String getUseAsUsername() {
 | 
				
			||||||
 | 
					                if (useAsUsername != null && useAsUsername.trim().length() > 0) {
 | 
				
			||||||
 | 
					                    return useAsUsername;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                return "email";
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            public void setUseAsUsername(String useAsUsername) {
 | 
				
			||||||
 | 
					                this.useAsUsername = useAsUsername;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            public String getProvider() {
 | 
				
			||||||
 | 
					                return provider;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            public void setProvider(String provider) {
 | 
				
			||||||
 | 
					                this.provider = provider;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            public Collection<String> getScopes() {
 | 
				
			||||||
 | 
					                return scopes;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            public void setScopes(String scpoes) {
 | 
				
			||||||
 | 
					                List<String> scopesList =
 | 
				
			||||||
 | 
					                        Arrays.stream(scpoes.split(","))
 | 
				
			||||||
 | 
					                                .map(String::trim)
 | 
				
			||||||
 | 
					                                .collect(Collectors.toList());
 | 
				
			||||||
 | 
					                this.scopes.addAll(scopesList);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            @Override
 | 
					            @Override
 | 
				
			||||||
            public String toString() {
 | 
					            public String toString() {
 | 
				
			||||||
                return "OAUTH2 [enabled="
 | 
					                return "OAUTH2 [enabled="
 | 
				
			||||||
@ -274,6 +313,12 @@ public class ApplicationProperties {
 | 
				
			|||||||
                        + (clientSecret != null && !clientSecret.isEmpty() ? "MASKED" : "NULL")
 | 
					                        + (clientSecret != null && !clientSecret.isEmpty() ? "MASKED" : "NULL")
 | 
				
			||||||
                        + ", autoCreateUser="
 | 
					                        + ", autoCreateUser="
 | 
				
			||||||
                        + autoCreateUser
 | 
					                        + autoCreateUser
 | 
				
			||||||
 | 
					                        + ", useAsUsername="
 | 
				
			||||||
 | 
					                        + useAsUsername
 | 
				
			||||||
 | 
					                        + ", provider"
 | 
				
			||||||
 | 
					                        + provider
 | 
				
			||||||
 | 
					                        + ", scopes="
 | 
				
			||||||
 | 
					                        + scopes
 | 
				
			||||||
                        + "]";
 | 
					                        + "]";
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -5,7 +5,7 @@ public class AttemptCounter {
 | 
				
			|||||||
    private long lastAttemptTime;
 | 
					    private long lastAttemptTime;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public AttemptCounter() {
 | 
					    public AttemptCounter() {
 | 
				
			||||||
        this.attemptCount = 1;
 | 
					        this.attemptCount = 0;
 | 
				
			||||||
        this.lastAttemptTime = System.currentTimeMillis();
 | 
					        this.lastAttemptTime = System.currentTimeMillis();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -18,11 +18,16 @@ public class AttemptCounter {
 | 
				
			|||||||
        return attemptCount;
 | 
					        return attemptCount;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public long getlastAttemptTime() {
 | 
					    public long getLastAttemptTime() {
 | 
				
			||||||
        return lastAttemptTime;
 | 
					        return lastAttemptTime;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public boolean shouldReset(long ATTEMPT_INCREMENT_TIME) {
 | 
					    public boolean shouldReset(long attemptIncrementTime) {
 | 
				
			||||||
        return System.currentTimeMillis() - lastAttemptTime > ATTEMPT_INCREMENT_TIME;
 | 
					        return System.currentTimeMillis() - lastAttemptTime > attemptIncrementTime;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public void reset() {
 | 
				
			||||||
 | 
					        this.attemptCount = 0;
 | 
				
			||||||
 | 
					        this.lastAttemptTime = System.currentTimeMillis();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					package stirling.software.SPDF.model;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public enum AuthenticationType {
 | 
				
			||||||
 | 
					    WEB,
 | 
				
			||||||
 | 
					    OAUTH2
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -47,6 +47,9 @@ public class User {
 | 
				
			|||||||
    @Column(name = "roleName")
 | 
					    @Column(name = "roleName")
 | 
				
			||||||
    private String roleName;
 | 
					    private String roleName;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Column(name = "authenticationtype")
 | 
				
			||||||
 | 
					    private String authenticationType;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "user")
 | 
					    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "user")
 | 
				
			||||||
    private Set<Authority> authorities = new HashSet<>();
 | 
					    private Set<Authority> authorities = new HashSet<>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -116,6 +119,14 @@ public class User {
 | 
				
			|||||||
        this.enabled = enabled;
 | 
					        this.enabled = enabled;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public void setAuthenticationType(AuthenticationType authenticationType) {
 | 
				
			||||||
 | 
					        this.authenticationType = authenticationType.toString().toLowerCase();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public String getAuthenticationType() {
 | 
				
			||||||
 | 
					        return authenticationType;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public Set<Authority> getAuthorities() {
 | 
					    public Set<Authority> getAuthorities() {
 | 
				
			||||||
        return authorities;
 | 
					        return authorities;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -137,4 +148,8 @@ public class User {
 | 
				
			|||||||
                .map(Authority::getAuthority)
 | 
					                .map(Authority::getAuthority)
 | 
				
			||||||
                .collect(Collectors.joining(", "));
 | 
					                .collect(Collectors.joining(", "));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public boolean hasPassword() {
 | 
				
			||||||
 | 
					        return this.password != null && !this.password.isEmpty();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -54,11 +54,13 @@ notAuthenticatedMessage=User not authenticated.
 | 
				
			|||||||
userNotFoundMessage=User not found.
 | 
					userNotFoundMessage=User not found.
 | 
				
			||||||
incorrectPasswordMessage=Current password is incorrect.
 | 
					incorrectPasswordMessage=Current password is incorrect.
 | 
				
			||||||
usernameExistsMessage=New Username already exists.
 | 
					usernameExistsMessage=New Username already exists.
 | 
				
			||||||
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
 | 
					invalidUsernameMessage=Invalid username, username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
 | 
				
			||||||
deleteCurrentUserMessage=Cannot delete currently logged in user.
 | 
					deleteCurrentUserMessage=Cannot delete currently logged in user.
 | 
				
			||||||
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
 | 
					deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
 | 
				
			||||||
downgradeCurrentUserMessage=لا يمكن خفض دور المستخدم الحالي
 | 
					downgradeCurrentUserMessage=لا يمكن خفض دور المستخدم الحالي
 | 
				
			||||||
downgradeCurrentUserLongMessage=لا يمكن تخفيض دور المستخدم الحالي. وبالتالي، لن يظهر المستخدم الحالي.
 | 
					downgradeCurrentUserLongMessage=لا يمكن تخفيض دور المستخدم الحالي. وبالتالي، لن يظهر المستخدم الحالي.
 | 
				
			||||||
 | 
					userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
 | 
				
			||||||
 | 
					userAlreadyExistsWebMessage=The user already exists as an web user.
 | 
				
			||||||
error=Error
 | 
					error=Error
 | 
				
			||||||
oops=Oops!
 | 
					oops=Oops!
 | 
				
			||||||
help=Help
 | 
					help=Help
 | 
				
			||||||
@ -72,6 +74,7 @@ sponsor=Sponsor
 | 
				
			|||||||
info=Info
 | 
					info=Info
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
###############
 | 
					###############
 | 
				
			||||||
#   Pipeline  #
 | 
					#   Pipeline  #
 | 
				
			||||||
###############
 | 
					###############
 | 
				
			||||||
@ -170,7 +173,7 @@ adminUserSettings.header=Admin User Control Settings
 | 
				
			|||||||
adminUserSettings.admin=Admin
 | 
					adminUserSettings.admin=Admin
 | 
				
			||||||
adminUserSettings.user=User
 | 
					adminUserSettings.user=User
 | 
				
			||||||
adminUserSettings.addUser=Add New User
 | 
					adminUserSettings.addUser=Add New User
 | 
				
			||||||
adminUserSettings.usernameInfo=Username must only contain letters and numbers, no spaces or special characters.
 | 
					adminUserSettings.usernameInfo=Username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
 | 
				
			||||||
adminUserSettings.roles=Roles
 | 
					adminUserSettings.roles=Roles
 | 
				
			||||||
adminUserSettings.role=Role
 | 
					adminUserSettings.role=Role
 | 
				
			||||||
adminUserSettings.actions=Actions
 | 
					adminUserSettings.actions=Actions
 | 
				
			||||||
@ -182,6 +185,7 @@ adminUserSettings.internalApiUser=Internal API User
 | 
				
			|||||||
adminUserSettings.forceChange=Force user to change password on login
 | 
					adminUserSettings.forceChange=Force user to change password on login
 | 
				
			||||||
adminUserSettings.submit=Save User
 | 
					adminUserSettings.submit=Save User
 | 
				
			||||||
adminUserSettings.changeUserRole=تغيير دور المستخدم
 | 
					adminUserSettings.changeUserRole=تغيير دور المستخدم
 | 
				
			||||||
 | 
					adminUserSettings.authenticated=Authenticated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#############
 | 
					#############
 | 
				
			||||||
# HOME-PAGE #
 | 
					# HOME-PAGE #
 | 
				
			||||||
@ -1056,7 +1060,7 @@ licenses.version=Version
 | 
				
			|||||||
licenses.license=License
 | 
					licenses.license=License
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# error
 | 
					#error
 | 
				
			||||||
error.sorry=Sorry for the issue!
 | 
					error.sorry=Sorry for the issue!
 | 
				
			||||||
error.needHelp=Need help / Found an issue?
 | 
					error.needHelp=Need help / Found an issue?
 | 
				
			||||||
error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
 | 
					error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
 | 
				
			||||||
 | 
				
			|||||||
@ -54,11 +54,13 @@ notAuthenticatedMessage=Потребителят не е автентикира
 | 
				
			|||||||
userNotFoundMessage=Потребителят не е намерен
 | 
					userNotFoundMessage=Потребителят не е намерен
 | 
				
			||||||
incorrectPasswordMessage=Текущата парола е неправилна.
 | 
					incorrectPasswordMessage=Текущата парола е неправилна.
 | 
				
			||||||
usernameExistsMessage=Новият потребител вече съществува.
 | 
					usernameExistsMessage=Новият потребител вече съществува.
 | 
				
			||||||
invalidUsernameMessage=Невалидно потребителско име, потребителското име трябва да съдържа само букви и цифри.
 | 
					invalidUsernameMessage=Невалидно потребителско име, потребителското име може да съдържа само букви, цифри и следните специални знаци @._+- или трябва да е валиден имейл адрес.
 | 
				
			||||||
deleteCurrentUserMessage=Не може да се изтрие вписания в момента потребител.
 | 
					deleteCurrentUserMessage=Не може да се изтрие вписания в момента потребител.
 | 
				
			||||||
deleteUsernameExistsMessage=Потребителското име не съществува и не може да бъде изтрито.
 | 
					deleteUsernameExistsMessage=Потребителското име не съществува и не може да бъде изтрито.
 | 
				
			||||||
downgradeCurrentUserMessage=Не може да се понижи ролята на текущия потребител
 | 
					downgradeCurrentUserMessage=Не може да се понижи ролята на текущия потребител
 | 
				
			||||||
downgradeCurrentUserLongMessage=Не може да се понижи ролята на текущия потребител. Следователно текущият потребител няма да бъде показан.
 | 
					downgradeCurrentUserLongMessage=Не може да се понижи ролята на текущия потребител. Следователно текущият потребител няма да бъде показан.
 | 
				
			||||||
 | 
					userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
 | 
				
			||||||
 | 
					userAlreadyExistsWebMessage=The user already exists as an web user.
 | 
				
			||||||
error=Грешка
 | 
					error=Грешка
 | 
				
			||||||
oops=Опаа!
 | 
					oops=Опаа!
 | 
				
			||||||
help=Помощ
 | 
					help=Помощ
 | 
				
			||||||
@ -73,7 +75,6 @@ info=Info
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
###############
 | 
					###############
 | 
				
			||||||
#   Pipeline  #
 | 
					#   Pipeline  #
 | 
				
			||||||
###############
 | 
					###############
 | 
				
			||||||
@ -172,7 +173,7 @@ adminUserSettings.header=Настройки за администраторск
 | 
				
			|||||||
adminUserSettings.admin=Администратор
 | 
					adminUserSettings.admin=Администратор
 | 
				
			||||||
adminUserSettings.user=Потребител
 | 
					adminUserSettings.user=Потребител
 | 
				
			||||||
adminUserSettings.addUser=Добавяне на нов потребител
 | 
					adminUserSettings.addUser=Добавяне на нов потребител
 | 
				
			||||||
adminUserSettings.usernameInfo=Потребителското име трябва да съдържа само букви и цифри, без интервали или специални знаци.
 | 
					adminUserSettings.usernameInfo=Потребителското име може да съдържа само букви, цифри и следните специални символи @._+- или трябва да е валиден имейл адрес.
 | 
				
			||||||
adminUserSettings.roles=Роли
 | 
					adminUserSettings.roles=Роли
 | 
				
			||||||
adminUserSettings.role=Роля
 | 
					adminUserSettings.role=Роля
 | 
				
			||||||
adminUserSettings.actions=Действия
 | 
					adminUserSettings.actions=Действия
 | 
				
			||||||
@ -184,6 +185,7 @@ adminUserSettings.internalApiUser=Вътрешен API потребител
 | 
				
			|||||||
adminUserSettings.forceChange=Принудете потребителя да промени потребителското име/парола при влизане
 | 
					adminUserSettings.forceChange=Принудете потребителя да промени потребителското име/парола при влизане
 | 
				
			||||||
adminUserSettings.submit=Съхранете потребителя
 | 
					adminUserSettings.submit=Съхранете потребителя
 | 
				
			||||||
adminUserSettings.changeUserRole=Промяна на ролята на потребителя
 | 
					adminUserSettings.changeUserRole=Промяна на ролята на потребителя
 | 
				
			||||||
 | 
					adminUserSettings.authenticated=Authenticated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#############
 | 
					#############
 | 
				
			||||||
# HOME-PAGE #
 | 
					# HOME-PAGE #
 | 
				
			||||||
@ -805,7 +807,6 @@ multiTool.title=PDF Мулти инструмент
 | 
				
			|||||||
multiTool.header=PDF Мулти инструмент
 | 
					multiTool.header=PDF Мулти инструмент
 | 
				
			||||||
multiTool.uploadPrompts=File Name
 | 
					multiTool.uploadPrompts=File Name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
#view pdf
 | 
					#view pdf
 | 
				
			||||||
viewPdf.title=Преглед на PDF
 | 
					viewPdf.title=Преглед на PDF
 | 
				
			||||||
viewPdf.header=Преглед на PDF
 | 
					viewPdf.header=Преглед на PDF
 | 
				
			||||||
@ -1059,7 +1060,7 @@ licenses.version=Версия
 | 
				
			|||||||
licenses.license=Лиценз
 | 
					licenses.license=Лиценз
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# error
 | 
					#error
 | 
				
			||||||
error.sorry=Извинете за проблема!
 | 
					error.sorry=Извинете за проблема!
 | 
				
			||||||
error.needHelp=Нуждаете се от помощ / Открихте проблем?
 | 
					error.needHelp=Нуждаете се от помощ / Открихте проблем?
 | 
				
			||||||
error.contactTip=Ако все още имате проблеми, не се колебайте да се свържете с нас за помощ. Можете да изпратите запитване на нашата страница в GitHub или да се свържете с нас чрез Discord:
 | 
					error.contactTip=Ако все още имате проблеми, не се колебайте да се свържете с нас за помощ. Можете да изпратите запитване на нашата страница в GitHub или да се свържете с нас чрез Discord:
 | 
				
			||||||
 | 
				
			|||||||
@ -54,11 +54,13 @@ notAuthenticatedMessage=User not authenticated.
 | 
				
			|||||||
userNotFoundMessage=User not found.
 | 
					userNotFoundMessage=User not found.
 | 
				
			||||||
incorrectPasswordMessage=Current password is incorrect.
 | 
					incorrectPasswordMessage=Current password is incorrect.
 | 
				
			||||||
usernameExistsMessage=New Username already exists.
 | 
					usernameExistsMessage=New Username already exists.
 | 
				
			||||||
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
 | 
					invalidUsernameMessage=Invalid username, username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
 | 
				
			||||||
deleteCurrentUserMessage=Cannot delete currently logged in user.
 | 
					deleteCurrentUserMessage=Cannot delete currently logged in user.
 | 
				
			||||||
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
 | 
					deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
 | 
				
			||||||
downgradeCurrentUserMessage=No es pot reduir la funció de l'usuari actual
 | 
					downgradeCurrentUserMessage=No es pot reduir la funció de l'usuari actual
 | 
				
			||||||
downgradeCurrentUserLongMessage=No es pot baixar la funció de l'usuari actual. Per tant, no es mostrarà l'usuari actual.
 | 
					downgradeCurrentUserLongMessage=No es pot baixar la funció de l'usuari actual. Per tant, no es mostrarà l'usuari actual.
 | 
				
			||||||
 | 
					userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
 | 
				
			||||||
 | 
					userAlreadyExistsWebMessage=The user already exists as an web user.
 | 
				
			||||||
error=Error
 | 
					error=Error
 | 
				
			||||||
oops=Oops!
 | 
					oops=Oops!
 | 
				
			||||||
help=Help
 | 
					help=Help
 | 
				
			||||||
@ -171,7 +173,7 @@ adminUserSettings.header=Usuari Admin Opcions Control
 | 
				
			|||||||
adminUserSettings.admin=Admin
 | 
					adminUserSettings.admin=Admin
 | 
				
			||||||
adminUserSettings.user=Usuari
 | 
					adminUserSettings.user=Usuari
 | 
				
			||||||
adminUserSettings.addUser=Afegir Usuari
 | 
					adminUserSettings.addUser=Afegir Usuari
 | 
				
			||||||
adminUserSettings.usernameInfo=Username must only contain letters and numbers, no spaces or special characters.
 | 
					adminUserSettings.usernameInfo=Username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
 | 
				
			||||||
adminUserSettings.roles=Rols
 | 
					adminUserSettings.roles=Rols
 | 
				
			||||||
adminUserSettings.role=Rol
 | 
					adminUserSettings.role=Rol
 | 
				
			||||||
adminUserSettings.actions=Accions
 | 
					adminUserSettings.actions=Accions
 | 
				
			||||||
@ -183,6 +185,7 @@ adminUserSettings.internalApiUser=Internal API User
 | 
				
			|||||||
adminUserSettings.forceChange=Force user to change password on login
 | 
					adminUserSettings.forceChange=Force user to change password on login
 | 
				
			||||||
adminUserSettings.submit=Desar Usuari
 | 
					adminUserSettings.submit=Desar Usuari
 | 
				
			||||||
adminUserSettings.changeUserRole=Canvia el rol de l'usuari
 | 
					adminUserSettings.changeUserRole=Canvia el rol de l'usuari
 | 
				
			||||||
 | 
					adminUserSettings.authenticated=Authenticated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#############
 | 
					#############
 | 
				
			||||||
# HOME-PAGE #
 | 
					# HOME-PAGE #
 | 
				
			||||||
@ -802,7 +805,7 @@ pdfOrganiser.placeholder=(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)
 | 
				
			|||||||
#multiTool
 | 
					#multiTool
 | 
				
			||||||
multiTool.title=PDF Multi Tool
 | 
					multiTool.title=PDF Multi Tool
 | 
				
			||||||
multiTool.header=PDF Multi Tool
 | 
					multiTool.header=PDF Multi Tool
 | 
				
			||||||
multiTool.uploadPrompts=Please Upload PDF
 | 
					multiTool.uploadPrompts=File Name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#view pdf
 | 
					#view pdf
 | 
				
			||||||
viewPdf.title=View PDF
 | 
					viewPdf.title=View PDF
 | 
				
			||||||
@ -1057,7 +1060,7 @@ licenses.version=Version
 | 
				
			|||||||
licenses.license=License
 | 
					licenses.license=License
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# error
 | 
					#error
 | 
				
			||||||
error.sorry=Sorry for the issue!
 | 
					error.sorry=Sorry for the issue!
 | 
				
			||||||
error.needHelp=Need help / Found an issue?
 | 
					error.needHelp=Need help / Found an issue?
 | 
				
			||||||
error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
 | 
					error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
 | 
				
			||||||
 | 
				
			|||||||
@ -54,11 +54,13 @@ notAuthenticatedMessage=Benutzer nicht authentifiziert.
 | 
				
			|||||||
userNotFoundMessage=Benutzer nicht gefunden.
 | 
					userNotFoundMessage=Benutzer nicht gefunden.
 | 
				
			||||||
incorrectPasswordMessage=Das Passwort ist falsch.
 | 
					incorrectPasswordMessage=Das Passwort ist falsch.
 | 
				
			||||||
usernameExistsMessage=Neuer Benutzername existiert bereits.
 | 
					usernameExistsMessage=Neuer Benutzername existiert bereits.
 | 
				
			||||||
invalidUsernameMessage=Ungültiger Benutzername. Der Benutzername darf nur Buchstaben und Zahlen enthalten.
 | 
					invalidUsernameMessage=Ungültiger Benutzername. Der Benutzername darf nur Buchstaben, Zahlen und die folgenden Sonderzeichen @._+- enthalten oder muss eine gültige E-Mail-Adresse sein.
 | 
				
			||||||
deleteCurrentUserMessage=Der aktuell angemeldete Benutzer kann nicht gelöscht werden.
 | 
					deleteCurrentUserMessage=Der aktuell angemeldete Benutzer kann nicht gelöscht werden.
 | 
				
			||||||
deleteUsernameExistsMessage=Der Benutzername existiert nicht und kann nicht gelöscht werden.
 | 
					deleteUsernameExistsMessage=Der Benutzername existiert nicht und kann nicht gelöscht werden.
 | 
				
			||||||
downgradeCurrentUserMessage=Die Rolle des aktuellen Benutzers kann nicht herabgestuft werden
 | 
					downgradeCurrentUserMessage=Die Rolle des aktuellen Benutzers kann nicht herabgestuft werden
 | 
				
			||||||
downgradeCurrentUserLongMessage=Die Rolle des aktuellen Benutzers kann nicht herabgestuft werden. Daher wird der aktuelle Benutzer nicht angezeigt.
 | 
					downgradeCurrentUserLongMessage=Die Rolle des aktuellen Benutzers kann nicht herabgestuft werden. Daher wird der aktuelle Benutzer nicht angezeigt.
 | 
				
			||||||
 | 
					userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
 | 
				
			||||||
 | 
					userAlreadyExistsWebMessage=The user already exists as an web user.
 | 
				
			||||||
error=Fehler
 | 
					error=Fehler
 | 
				
			||||||
oops=Hoppla!
 | 
					oops=Hoppla!
 | 
				
			||||||
help=Hilfe
 | 
					help=Hilfe
 | 
				
			||||||
@ -171,7 +173,7 @@ adminUserSettings.header=Administrator-Benutzerkontrolle
 | 
				
			|||||||
adminUserSettings.admin=Administrator
 | 
					adminUserSettings.admin=Administrator
 | 
				
			||||||
adminUserSettings.user=Benutzer
 | 
					adminUserSettings.user=Benutzer
 | 
				
			||||||
adminUserSettings.addUser=Neuen Benutzer hinzufügen
 | 
					adminUserSettings.addUser=Neuen Benutzer hinzufügen
 | 
				
			||||||
adminUserSettings.usernameInfo=Der Benutzername darf nur Buchstaben und Zahlen enthalten, keine Leerzeichen oder Sonderzeichen.
 | 
					adminUserSettings.usernameInfo=Der Benutzername darf nur Buchstaben, Zahlen und die folgenden Sonderzeichen @._+- enthalten oder muss eine gültige E-Mail-Adresse sein.
 | 
				
			||||||
adminUserSettings.roles=Rollen
 | 
					adminUserSettings.roles=Rollen
 | 
				
			||||||
adminUserSettings.role=Rolle
 | 
					adminUserSettings.role=Rolle
 | 
				
			||||||
adminUserSettings.actions=Aktion
 | 
					adminUserSettings.actions=Aktion
 | 
				
			||||||
@ -183,6 +185,7 @@ adminUserSettings.internalApiUser=Interner API-Benutzer
 | 
				
			|||||||
adminUserSettings.forceChange=Benutzer dazu zwingen, Benutzernamen/Passwort bei der Anmeldung zu ändern
 | 
					adminUserSettings.forceChange=Benutzer dazu zwingen, Benutzernamen/Passwort bei der Anmeldung zu ändern
 | 
				
			||||||
adminUserSettings.submit=Benutzer speichern
 | 
					adminUserSettings.submit=Benutzer speichern
 | 
				
			||||||
adminUserSettings.changeUserRole=Benutzerrolle ändern
 | 
					adminUserSettings.changeUserRole=Benutzerrolle ändern
 | 
				
			||||||
 | 
					adminUserSettings.authenticated=Authenticated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#############
 | 
					#############
 | 
				
			||||||
# HOME-PAGE #
 | 
					# HOME-PAGE #
 | 
				
			||||||
@ -802,7 +805,7 @@ pdfOrganiser.placeholder=(z.B. 1,3,2 oder 4-8,2,10-12 oder 2n-1)
 | 
				
			|||||||
#multiTool
 | 
					#multiTool
 | 
				
			||||||
multiTool.title=PDF-Multitool
 | 
					multiTool.title=PDF-Multitool
 | 
				
			||||||
multiTool.header=PDF-Multitool
 | 
					multiTool.header=PDF-Multitool
 | 
				
			||||||
multiTool.uploadPrompts=Bitte PDF hochladen
 | 
					multiTool.uploadPrompts=File Name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#view pdf
 | 
					#view pdf
 | 
				
			||||||
viewPdf.title=PDF anzeigen
 | 
					viewPdf.title=PDF anzeigen
 | 
				
			||||||
@ -1057,7 +1060,7 @@ licenses.version=Version
 | 
				
			|||||||
licenses.license=Lizenz
 | 
					licenses.license=Lizenz
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# error
 | 
					#error
 | 
				
			||||||
error.sorry=Entschuldigung für das Problem!
 | 
					error.sorry=Entschuldigung für das Problem!
 | 
				
			||||||
error.needHelp=Brauchst du Hilfe / Ein Problem gefunden?
 | 
					error.needHelp=Brauchst du Hilfe / Ein Problem gefunden?
 | 
				
			||||||
error.contactTip=Wenn du weiterhin Probleme hast, zögere nicht, uns um Hilfe zu bitten. Du kannst ein Ticket auf unserer GitHub-Seite einreichen oder uns über Discord kontaktieren:
 | 
					error.contactTip=Wenn du weiterhin Probleme hast, zögere nicht, uns um Hilfe zu bitten. Du kannst ein Ticket auf unserer GitHub-Seite einreichen oder uns über Discord kontaktieren:
 | 
				
			||||||
 | 
				
			|||||||
@ -54,11 +54,13 @@ notAuthenticatedMessage=Ο χρήστης δεν έχει αυθεντικοπο
 | 
				
			|||||||
userNotFoundMessage=Ο χρήστης δεν βρέθηκε.
 | 
					userNotFoundMessage=Ο χρήστης δεν βρέθηκε.
 | 
				
			||||||
incorrectPasswordMessage=Ο τρέχων κωδικός πρόσβασης είναι λανθασμένος.
 | 
					incorrectPasswordMessage=Ο τρέχων κωδικός πρόσβασης είναι λανθασμένος.
 | 
				
			||||||
usernameExistsMessage=Το νέο όνομα χρήστη υπάρχει ήδη.
 | 
					usernameExistsMessage=Το νέο όνομα χρήστη υπάρχει ήδη.
 | 
				
			||||||
invalidUsernameMessage=Μη έγκυρο όνομα χρήστη, το όνομα χρήστη πρέπει να περιέχει μόνο αλφαβητικούς χαρακτήρες και αριθμούς.
 | 
					invalidUsernameMessage=Μη έγκυρο όνομα χρήστη, όνομα χρήστη μπορεί να περιέχει μόνο γράμματα, αριθμούς και τους ακόλουθους ειδικούς χαρακτήρες @._+- ή πρέπει να είναι έγκυρη διεύθυνση email.
 | 
				
			||||||
deleteCurrentUserMessage=Δεν είναι δυνατή η διαγραφή του τρέχοντος συνδεδεμένου χρήστη.
 | 
					deleteCurrentUserMessage=Δεν είναι δυνατή η διαγραφή του τρέχοντος συνδεδεμένου χρήστη.
 | 
				
			||||||
deleteUsernameExistsMessage=Το όνομα χρήστη δεν υπάρχει και δεν μπορεί να διαγραφεί.
 | 
					deleteUsernameExistsMessage=Το όνομα χρήστη δεν υπάρχει και δεν μπορεί να διαγραφεί.
 | 
				
			||||||
downgradeCurrentUserMessage=Δεν είναι δυνατή η υποβάθμιση του ρόλου του τρέχοντος χρήστη
 | 
					downgradeCurrentUserMessage=Δεν είναι δυνατή η υποβάθμιση του ρόλου του τρέχοντος χρήστη
 | 
				
			||||||
downgradeCurrentUserLongMessage=Δεν είναι δυνατή η υποβάθμιση του ρόλου του τρέχοντος χρήστη. Ως εκ τούτου, ο τρέχων χρήστης δεν θα εμφανίζεται.
 | 
					downgradeCurrentUserLongMessage=Δεν είναι δυνατή η υποβάθμιση του ρόλου του τρέχοντος χρήστη. Ως εκ τούτου, ο τρέχων χρήστης δεν θα εμφανίζεται.
 | 
				
			||||||
 | 
					userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
 | 
				
			||||||
 | 
					userAlreadyExistsWebMessage=The user already exists as an web user.
 | 
				
			||||||
error=Σφάλμα
 | 
					error=Σφάλμα
 | 
				
			||||||
oops=Ωχ!
 | 
					oops=Ωχ!
 | 
				
			||||||
help=Βοήθεια
 | 
					help=Βοήθεια
 | 
				
			||||||
@ -171,7 +173,7 @@ adminUserSettings.header=Ρυθμίσεις ελέγχου Διαχειριστ
 | 
				
			|||||||
adminUserSettings.admin=Διαχειριστής
 | 
					adminUserSettings.admin=Διαχειριστής
 | 
				
			||||||
adminUserSettings.user=Χρήστης
 | 
					adminUserSettings.user=Χρήστης
 | 
				
			||||||
adminUserSettings.addUser=Προσθήκη νέου Χρήστη
 | 
					adminUserSettings.addUser=Προσθήκη νέου Χρήστη
 | 
				
			||||||
adminUserSettings.usernameInfo=Username must only contain letters and numbers, no spaces or special characters.
 | 
					adminUserSettings.usernameInfo=Username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
 | 
				
			||||||
adminUserSettings.roles=Ρόλοι
 | 
					adminUserSettings.roles=Ρόλοι
 | 
				
			||||||
adminUserSettings.role=Ρόλος
 | 
					adminUserSettings.role=Ρόλος
 | 
				
			||||||
adminUserSettings.actions=Ενέργειες
 | 
					adminUserSettings.actions=Ενέργειες
 | 
				
			||||||
@ -183,6 +185,7 @@ adminUserSettings.internalApiUser=Εσωτερικός API χρήστης
 | 
				
			|||||||
adminUserSettings.forceChange=Αναγκάστε τον χρήστη να αλλάξει το όνομα χρήστη/κωδικό πρόσβασης κατά τη σύνδεση
 | 
					adminUserSettings.forceChange=Αναγκάστε τον χρήστη να αλλάξει το όνομα χρήστη/κωδικό πρόσβασης κατά τη σύνδεση
 | 
				
			||||||
adminUserSettings.submit=Αποθήκευση Χρήστη
 | 
					adminUserSettings.submit=Αποθήκευση Χρήστη
 | 
				
			||||||
adminUserSettings.changeUserRole=Αλλαγή ρόλου χρήστη
 | 
					adminUserSettings.changeUserRole=Αλλαγή ρόλου χρήστη
 | 
				
			||||||
 | 
					adminUserSettings.authenticated=Authenticated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#############
 | 
					#############
 | 
				
			||||||
# HOME-PAGE #
 | 
					# HOME-PAGE #
 | 
				
			||||||
@ -802,7 +805,7 @@ pdfOrganiser.placeholder=(π.χ. 1,3,2 ή 4-8,2,10-12 ή 2n-1)
 | 
				
			|||||||
#multiTool
 | 
					#multiTool
 | 
				
			||||||
multiTool.title=PDF Πολυεργαλείο
 | 
					multiTool.title=PDF Πολυεργαλείο
 | 
				
			||||||
multiTool.header=PDF Πολυεργαλείο
 | 
					multiTool.header=PDF Πολυεργαλείο
 | 
				
			||||||
multiTool.uploadPrompts=Ανεβάστε το PDF
 | 
					multiTool.uploadPrompts=File Name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#view pdf
 | 
					#view pdf
 | 
				
			||||||
viewPdf.title=Προβολή PDF
 | 
					viewPdf.title=Προβολή PDF
 | 
				
			||||||
@ -1057,7 +1060,7 @@ licenses.version=Εκδοχή
 | 
				
			|||||||
licenses.license=Άδεια
 | 
					licenses.license=Άδεια
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# error
 | 
					#error
 | 
				
			||||||
error.sorry=Συγγνώμη για το ζήτημα!
 | 
					error.sorry=Συγγνώμη για το ζήτημα!
 | 
				
			||||||
error.needHelp=Χρειάζεστε βοήθεια / Βρήκατε πρόβλημα;
 | 
					error.needHelp=Χρειάζεστε βοήθεια / Βρήκατε πρόβλημα;
 | 
				
			||||||
error.contactTip=Εάν εξακολουθείτε να αντιμετωπίζετε προβλήματα, μη διστάσετε να επικοινωνήσετε μαζί μας για βοήθεια. Μπορείτε να υποβάλετε ένα ticket στη σελίδα μας στο GitHub ή να επικοινωνήσετε μαζί μας μέσω του Discord:
 | 
					error.contactTip=Εάν εξακολουθείτε να αντιμετωπίζετε προβλήματα, μη διστάσετε να επικοινωνήσετε μαζί μας για βοήθεια. Μπορείτε να υποβάλετε ένα ticket στη σελίδα μας στο GitHub ή να επικοινωνήσετε μαζί μας μέσω του Discord:
 | 
				
			||||||
 | 
				
			|||||||
@ -54,11 +54,13 @@ notAuthenticatedMessage=User not authenticated.
 | 
				
			|||||||
userNotFoundMessage=User not found.
 | 
					userNotFoundMessage=User not found.
 | 
				
			||||||
incorrectPasswordMessage=Current password is incorrect.
 | 
					incorrectPasswordMessage=Current password is incorrect.
 | 
				
			||||||
usernameExistsMessage=New Username already exists.
 | 
					usernameExistsMessage=New Username already exists.
 | 
				
			||||||
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
 | 
					invalidUsernameMessage=Invalid username, username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
 | 
				
			||||||
deleteCurrentUserMessage=Cannot delete currently logged in user.
 | 
					deleteCurrentUserMessage=Cannot delete currently logged in user.
 | 
				
			||||||
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
 | 
					deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
 | 
				
			||||||
downgradeCurrentUserMessage=Cannot downgrade current user's role
 | 
					downgradeCurrentUserMessage=Cannot downgrade current user's role
 | 
				
			||||||
downgradeCurrentUserLongMessage=Cannot downgrade current user's role. Hence, current user will not be shown.
 | 
					downgradeCurrentUserLongMessage=Cannot downgrade current user's role. Hence, current user will not be shown.
 | 
				
			||||||
 | 
					userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
 | 
				
			||||||
 | 
					userAlreadyExistsWebMessage=The user already exists as an web user.
 | 
				
			||||||
error=Error
 | 
					error=Error
 | 
				
			||||||
oops=Oops!
 | 
					oops=Oops!
 | 
				
			||||||
help=Help
 | 
					help=Help
 | 
				
			||||||
@ -171,7 +173,7 @@ adminUserSettings.header=Admin User Control Settings
 | 
				
			|||||||
adminUserSettings.admin=Admin
 | 
					adminUserSettings.admin=Admin
 | 
				
			||||||
adminUserSettings.user=User
 | 
					adminUserSettings.user=User
 | 
				
			||||||
adminUserSettings.addUser=Add New User
 | 
					adminUserSettings.addUser=Add New User
 | 
				
			||||||
adminUserSettings.usernameInfo=Username must only contain letters and numbers, no spaces or special characters.
 | 
					adminUserSettings.usernameInfo=Username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
 | 
				
			||||||
adminUserSettings.roles=Roles
 | 
					adminUserSettings.roles=Roles
 | 
				
			||||||
adminUserSettings.role=Role
 | 
					adminUserSettings.role=Role
 | 
				
			||||||
adminUserSettings.actions=Actions
 | 
					adminUserSettings.actions=Actions
 | 
				
			||||||
@ -183,6 +185,7 @@ adminUserSettings.internalApiUser=Internal API User
 | 
				
			|||||||
adminUserSettings.forceChange=Force user to change password on login
 | 
					adminUserSettings.forceChange=Force user to change password on login
 | 
				
			||||||
adminUserSettings.submit=Save User
 | 
					adminUserSettings.submit=Save User
 | 
				
			||||||
adminUserSettings.changeUserRole=Change User's Role
 | 
					adminUserSettings.changeUserRole=Change User's Role
 | 
				
			||||||
 | 
					adminUserSettings.authenticated=Authenticated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#############
 | 
					#############
 | 
				
			||||||
# HOME-PAGE #
 | 
					# HOME-PAGE #
 | 
				
			||||||
@ -1057,7 +1060,7 @@ licenses.version=Version
 | 
				
			|||||||
licenses.license=License
 | 
					licenses.license=License
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# error
 | 
					#error
 | 
				
			||||||
error.sorry=Sorry for the issue!
 | 
					error.sorry=Sorry for the issue!
 | 
				
			||||||
error.needHelp=Need help / Found an issue?
 | 
					error.needHelp=Need help / Found an issue?
 | 
				
			||||||
error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
 | 
					error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
 | 
				
			||||||
 | 
				
			|||||||
@ -54,11 +54,13 @@ notAuthenticatedMessage=User not authenticated.
 | 
				
			|||||||
userNotFoundMessage=User not found.
 | 
					userNotFoundMessage=User not found.
 | 
				
			||||||
incorrectPasswordMessage=Current password is incorrect.
 | 
					incorrectPasswordMessage=Current password is incorrect.
 | 
				
			||||||
usernameExistsMessage=New Username already exists.
 | 
					usernameExistsMessage=New Username already exists.
 | 
				
			||||||
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
 | 
					invalidUsernameMessage=Invalid username, username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
 | 
				
			||||||
deleteCurrentUserMessage=Cannot delete currently logged in user.
 | 
					deleteCurrentUserMessage=Cannot delete currently logged in user.
 | 
				
			||||||
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
 | 
					deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
 | 
				
			||||||
downgradeCurrentUserMessage=Cannot downgrade current user's role
 | 
					downgradeCurrentUserMessage=Cannot downgrade current user's role
 | 
				
			||||||
downgradeCurrentUserLongMessage=Cannot downgrade current user's role. Hence, current user will not be shown.
 | 
					downgradeCurrentUserLongMessage=Cannot downgrade current user's role. Hence, current user will not be shown.
 | 
				
			||||||
 | 
					userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
 | 
				
			||||||
 | 
					userAlreadyExistsWebMessage=The user already exists as an web user.
 | 
				
			||||||
error=Error
 | 
					error=Error
 | 
				
			||||||
oops=Oops!
 | 
					oops=Oops!
 | 
				
			||||||
help=Help
 | 
					help=Help
 | 
				
			||||||
@ -72,6 +74,7 @@ sponsor=Sponsor
 | 
				
			|||||||
info=Info
 | 
					info=Info
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
###############
 | 
					###############
 | 
				
			||||||
#   Pipeline  #
 | 
					#   Pipeline  #
 | 
				
			||||||
###############
 | 
					###############
 | 
				
			||||||
@ -170,7 +173,7 @@ adminUserSettings.header=Admin User Control Settings
 | 
				
			|||||||
adminUserSettings.admin=Admin
 | 
					adminUserSettings.admin=Admin
 | 
				
			||||||
adminUserSettings.user=User
 | 
					adminUserSettings.user=User
 | 
				
			||||||
adminUserSettings.addUser=Add New User
 | 
					adminUserSettings.addUser=Add New User
 | 
				
			||||||
adminUserSettings.usernameInfo=Username must only contain letters and numbers, no spaces or special characters.
 | 
					adminUserSettings.usernameInfo=Username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
 | 
				
			||||||
adminUserSettings.roles=Roles
 | 
					adminUserSettings.roles=Roles
 | 
				
			||||||
adminUserSettings.role=Role
 | 
					adminUserSettings.role=Role
 | 
				
			||||||
adminUserSettings.actions=Actions
 | 
					adminUserSettings.actions=Actions
 | 
				
			||||||
@ -182,6 +185,7 @@ adminUserSettings.internalApiUser=Internal API User
 | 
				
			|||||||
adminUserSettings.forceChange=Force user to change password on login
 | 
					adminUserSettings.forceChange=Force user to change password on login
 | 
				
			||||||
adminUserSettings.submit=Save User
 | 
					adminUserSettings.submit=Save User
 | 
				
			||||||
adminUserSettings.changeUserRole=Change User's Role
 | 
					adminUserSettings.changeUserRole=Change User's Role
 | 
				
			||||||
 | 
					adminUserSettings.authenticated=Authenticated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#############
 | 
					#############
 | 
				
			||||||
# HOME-PAGE #
 | 
					# HOME-PAGE #
 | 
				
			||||||
@ -1056,7 +1060,7 @@ licenses.version=Version
 | 
				
			|||||||
licenses.license=License
 | 
					licenses.license=License
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# error
 | 
					#error
 | 
				
			||||||
error.sorry=Sorry for the issue!
 | 
					error.sorry=Sorry for the issue!
 | 
				
			||||||
error.needHelp=Need help / Found an issue?
 | 
					error.needHelp=Need help / Found an issue?
 | 
				
			||||||
error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
 | 
					error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
 | 
				
			||||||
 | 
				
			|||||||
@ -54,11 +54,13 @@ notAuthenticatedMessage=Usuario no autentificado.
 | 
				
			|||||||
userNotFoundMessage=Usuario no encontrado.
 | 
					userNotFoundMessage=Usuario no encontrado.
 | 
				
			||||||
incorrectPasswordMessage=La contraseña actual no es correcta.
 | 
					incorrectPasswordMessage=La contraseña actual no es correcta.
 | 
				
			||||||
usernameExistsMessage=El nuevo nombre de usuario está en uso.
 | 
					usernameExistsMessage=El nuevo nombre de usuario está en uso.
 | 
				
			||||||
invalidUsernameMessage=Nombre de usuario no válido, El nombre de ususario debe contener únicamente números y caracteres alfabéticos.
 | 
					invalidUsernameMessage=Nombre de usuario no válido, el nombre de usuario solo puede contener letras, números y los siguientes caracteres especiales @._+- o debe ser una dirección de correo electrónico válida.
 | 
				
			||||||
deleteCurrentUserMessage=No puede eliminar el usuario que tiene la sesión actualmente en uso.
 | 
					deleteCurrentUserMessage=No puede eliminar el usuario que tiene la sesión actualmente en uso.
 | 
				
			||||||
deleteUsernameExistsMessage=El usuario no existe y no puede eliminarse.
 | 
					deleteUsernameExistsMessage=El usuario no existe y no puede eliminarse.
 | 
				
			||||||
downgradeCurrentUserMessage=No se puede degradar el rol del usuario actual
 | 
					downgradeCurrentUserMessage=No se puede degradar el rol del usuario actual
 | 
				
			||||||
downgradeCurrentUserLongMessage=No se puede degradar el rol del usuario actual. Por lo tanto, el usuario actual no se mostrará.
 | 
					downgradeCurrentUserLongMessage=No se puede degradar el rol del usuario actual. Por lo tanto, el usuario actual no se mostrará.
 | 
				
			||||||
 | 
					userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
 | 
				
			||||||
 | 
					userAlreadyExistsWebMessage=The user already exists as an web user.
 | 
				
			||||||
error=Error
 | 
					error=Error
 | 
				
			||||||
oops=Ups!
 | 
					oops=Ups!
 | 
				
			||||||
help=Help
 | 
					help=Help
 | 
				
			||||||
@ -73,9 +75,6 @@ info=Info
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
###############
 | 
					###############
 | 
				
			||||||
#   Pipeline  #
 | 
					#   Pipeline  #
 | 
				
			||||||
###############
 | 
					###############
 | 
				
			||||||
@ -174,7 +173,7 @@ adminUserSettings.header=Configuración de control de usuario administrador
 | 
				
			|||||||
adminUserSettings.admin=Administrador
 | 
					adminUserSettings.admin=Administrador
 | 
				
			||||||
adminUserSettings.user=Usuario
 | 
					adminUserSettings.user=Usuario
 | 
				
			||||||
adminUserSettings.addUser=Añadir Nuevo Usuario
 | 
					adminUserSettings.addUser=Añadir Nuevo Usuario
 | 
				
			||||||
adminUserSettings.usernameInfo=El nombrede usuario debe contener únicamente letras y números, no espacios ni caracteres especiales.
 | 
					adminUserSettings.usernameInfo=El nombre de usuario solo puede contener letras, números y los siguientes caracteres especiales @._+- o debe ser una dirección de correo electrónico válida.
 | 
				
			||||||
adminUserSettings.roles=Roles
 | 
					adminUserSettings.roles=Roles
 | 
				
			||||||
adminUserSettings.role=Rol
 | 
					adminUserSettings.role=Rol
 | 
				
			||||||
adminUserSettings.actions=Acciones
 | 
					adminUserSettings.actions=Acciones
 | 
				
			||||||
@ -186,6 +185,7 @@ adminUserSettings.internalApiUser=Usuario interno de API
 | 
				
			|||||||
adminUserSettings.forceChange=Forzar usuario a cambiar usuario/contraseña en el acceso
 | 
					adminUserSettings.forceChange=Forzar usuario a cambiar usuario/contraseña en el acceso
 | 
				
			||||||
adminUserSettings.submit=Guardar Usuario
 | 
					adminUserSettings.submit=Guardar Usuario
 | 
				
			||||||
adminUserSettings.changeUserRole=Cambiar rol de usuario
 | 
					adminUserSettings.changeUserRole=Cambiar rol de usuario
 | 
				
			||||||
 | 
					adminUserSettings.authenticated=Authenticated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#############
 | 
					#############
 | 
				
			||||||
# HOME-PAGE #
 | 
					# HOME-PAGE #
 | 
				
			||||||
@ -805,7 +805,7 @@ pdfOrganiser.placeholder=(por ej., 1,3,2 o 4-8,2,10-12 o 2n-1)
 | 
				
			|||||||
#multiTool
 | 
					#multiTool
 | 
				
			||||||
multiTool.title=Multi-herramienta PDF
 | 
					multiTool.title=Multi-herramienta PDF
 | 
				
			||||||
multiTool.header=Multi-herramienta PDF
 | 
					multiTool.header=Multi-herramienta PDF
 | 
				
			||||||
multiTool.uploadPrompts=Por favor, cargue PDF
 | 
					multiTool.uploadPrompts=File Name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#view pdf
 | 
					#view pdf
 | 
				
			||||||
viewPdf.title=Ver PDF
 | 
					viewPdf.title=Ver PDF
 | 
				
			||||||
@ -1060,7 +1060,7 @@ licenses.version=Versión
 | 
				
			|||||||
licenses.license=Licencia
 | 
					licenses.license=Licencia
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# error
 | 
					#error
 | 
				
			||||||
error.sorry=¡Perdón por el fallo!
 | 
					error.sorry=¡Perdón por el fallo!
 | 
				
			||||||
error.needHelp=Necesita ayuda / Encontró un fallo?
 | 
					error.needHelp=Necesita ayuda / Encontró un fallo?
 | 
				
			||||||
error.contactTip=Si sigue experimentando errores, no dude en contactarnos para solicitar soporte. Puede enviarnos un ticket en la página de GitHub o contactarnos mediante Discord:
 | 
					error.contactTip=Si sigue experimentando errores, no dude en contactarnos para solicitar soporte. Puede enviarnos un ticket en la página de GitHub o contactarnos mediante Discord:
 | 
				
			||||||
 | 
				
			|||||||
@ -54,11 +54,13 @@ notAuthenticatedMessage=User not authenticated.
 | 
				
			|||||||
userNotFoundMessage=User not found.
 | 
					userNotFoundMessage=User not found.
 | 
				
			||||||
incorrectPasswordMessage=Current password is incorrect.
 | 
					incorrectPasswordMessage=Current password is incorrect.
 | 
				
			||||||
usernameExistsMessage=New Username already exists.
 | 
					usernameExistsMessage=New Username already exists.
 | 
				
			||||||
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
 | 
					invalidUsernameMessage=Invalid username, username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
 | 
				
			||||||
deleteCurrentUserMessage=Cannot delete currently logged in user.
 | 
					deleteCurrentUserMessage=Cannot delete currently logged in user.
 | 
				
			||||||
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
 | 
					deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
 | 
				
			||||||
downgradeCurrentUserMessage=Ezin da uneko erabiltzailearen rola jaitsi
 | 
					downgradeCurrentUserMessage=Ezin da uneko erabiltzailearen rola jaitsi
 | 
				
			||||||
downgradeCurrentUserLongMessage=Ezin da uneko erabiltzailearen rola jaitsi. Beraz, oraingo erabiltzailea ez da erakutsiko.
 | 
					downgradeCurrentUserLongMessage=Ezin da uneko erabiltzailearen rola jaitsi. Beraz, oraingo erabiltzailea ez da erakutsiko.
 | 
				
			||||||
 | 
					userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
 | 
				
			||||||
 | 
					userAlreadyExistsWebMessage=The user already exists as an web user.
 | 
				
			||||||
error=Error
 | 
					error=Error
 | 
				
			||||||
oops=Oops!
 | 
					oops=Oops!
 | 
				
			||||||
help=Help
 | 
					help=Help
 | 
				
			||||||
@ -171,7 +173,7 @@ adminUserSettings.header=Admin Erabiltzailearen Ezarpenen Kontrolak
 | 
				
			|||||||
adminUserSettings.admin=Admin
 | 
					adminUserSettings.admin=Admin
 | 
				
			||||||
adminUserSettings.user=Erabiltzaile
 | 
					adminUserSettings.user=Erabiltzaile
 | 
				
			||||||
adminUserSettings.addUser=Erabiltzaile berria
 | 
					adminUserSettings.addUser=Erabiltzaile berria
 | 
				
			||||||
adminUserSettings.usernameInfo=Username must only contain letters and numbers, no spaces or special characters.
 | 
					adminUserSettings.usernameInfo=Username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
 | 
				
			||||||
adminUserSettings.roles=Rolak
 | 
					adminUserSettings.roles=Rolak
 | 
				
			||||||
adminUserSettings.role=Rol
 | 
					adminUserSettings.role=Rol
 | 
				
			||||||
adminUserSettings.actions=Ekintzak
 | 
					adminUserSettings.actions=Ekintzak
 | 
				
			||||||
@ -183,6 +185,7 @@ adminUserSettings.internalApiUser=Internal API User
 | 
				
			|||||||
adminUserSettings.forceChange=Force user to change password on login
 | 
					adminUserSettings.forceChange=Force user to change password on login
 | 
				
			||||||
adminUserSettings.submit=Gorde Erabiltzailea
 | 
					adminUserSettings.submit=Gorde Erabiltzailea
 | 
				
			||||||
adminUserSettings.changeUserRole=Erabiltzailearen rola aldatu
 | 
					adminUserSettings.changeUserRole=Erabiltzailearen rola aldatu
 | 
				
			||||||
 | 
					adminUserSettings.authenticated=Authenticated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#############
 | 
					#############
 | 
				
			||||||
# HOME-PAGE #
 | 
					# HOME-PAGE #
 | 
				
			||||||
@ -802,7 +805,7 @@ pdfOrganiser.placeholder=(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)
 | 
				
			|||||||
#multiTool
 | 
					#multiTool
 | 
				
			||||||
multiTool.title=PDF erabilera anitzeko tresna
 | 
					multiTool.title=PDF erabilera anitzeko tresna
 | 
				
			||||||
multiTool.header=PDF erabilera anitzeko tresna
 | 
					multiTool.header=PDF erabilera anitzeko tresna
 | 
				
			||||||
multiTool.uploadPrompts=Please Upload PDF
 | 
					multiTool.uploadPrompts=File Name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#view pdf
 | 
					#view pdf
 | 
				
			||||||
viewPdf.title=View PDF
 | 
					viewPdf.title=View PDF
 | 
				
			||||||
@ -1057,7 +1060,7 @@ licenses.version=Version
 | 
				
			|||||||
licenses.license=License
 | 
					licenses.license=License
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# error
 | 
					#error
 | 
				
			||||||
error.sorry=Sorry for the issue!
 | 
					error.sorry=Sorry for the issue!
 | 
				
			||||||
error.needHelp=Need help / Found an issue?
 | 
					error.needHelp=Need help / Found an issue?
 | 
				
			||||||
error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
 | 
					error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
 | 
				
			||||||
 | 
				
			|||||||
@ -54,11 +54,13 @@ notAuthenticatedMessage=Utilisateur non authentifié.
 | 
				
			|||||||
userNotFoundMessage=Utilisateur non trouvé.
 | 
					userNotFoundMessage=Utilisateur non trouvé.
 | 
				
			||||||
incorrectPasswordMessage=Le mot de passe actuel est incorrect.
 | 
					incorrectPasswordMessage=Le mot de passe actuel est incorrect.
 | 
				
			||||||
usernameExistsMessage=Le nouveau nom d’utilisateur existe déjà.
 | 
					usernameExistsMessage=Le nouveau nom d’utilisateur existe déjà.
 | 
				
			||||||
invalidUsernameMessage=Nom d’utilisateur invalide, le nom d’utilisateur ne peut contenir que des chiffres et des lettres.
 | 
					invalidUsernameMessage=Nom d’utilisateur invalide, le nom d’utilisateur ne peut contenir que des lettres, des chiffres et les caractères spéciaux suivants @._+- ou doit être une adresse e-mail valide.
 | 
				
			||||||
deleteCurrentUserMessage=Impossible de supprimer l’utilisateur actuellement connecté.
 | 
					deleteCurrentUserMessage=Impossible de supprimer l’utilisateur actuellement connecté.
 | 
				
			||||||
deleteUsernameExistsMessage=Le nom d’utilisateur n’existe pas et ne peut pas être supprimé.
 | 
					deleteUsernameExistsMessage=Le nom d’utilisateur n’existe pas et ne peut pas être supprimé.
 | 
				
			||||||
downgradeCurrentUserMessage=Impossible de rétrograder le rôle de l'utilisateur actuel
 | 
					downgradeCurrentUserMessage=Impossible de rétrograder le rôle de l'utilisateur actuel
 | 
				
			||||||
downgradeCurrentUserLongMessage=Impossible de rétrograder le rôle de l'utilisateur actuel. Par conséquent, l'utilisateur actuel ne sera pas affiché.
 | 
					downgradeCurrentUserLongMessage=Impossible de rétrograder le rôle de l'utilisateur actuel. Par conséquent, l'utilisateur actuel ne sera pas affiché.
 | 
				
			||||||
 | 
					userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
 | 
				
			||||||
 | 
					userAlreadyExistsWebMessage=The user already exists as an web user.
 | 
				
			||||||
error=Erreur
 | 
					error=Erreur
 | 
				
			||||||
oops=Oups !
 | 
					oops=Oups !
 | 
				
			||||||
help=Aide
 | 
					help=Aide
 | 
				
			||||||
@ -72,6 +74,7 @@ sponsor=Sponsor
 | 
				
			|||||||
info=Info
 | 
					info=Info
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
###############
 | 
					###############
 | 
				
			||||||
#   Pipeline  #
 | 
					#   Pipeline  #
 | 
				
			||||||
###############
 | 
					###############
 | 
				
			||||||
@ -170,7 +173,7 @@ adminUserSettings.header=Administration des paramètres des utilisateurs
 | 
				
			|||||||
adminUserSettings.admin=Administateur
 | 
					adminUserSettings.admin=Administateur
 | 
				
			||||||
adminUserSettings.user=Utilisateur
 | 
					adminUserSettings.user=Utilisateur
 | 
				
			||||||
adminUserSettings.addUser=Ajouter un utilisateur
 | 
					adminUserSettings.addUser=Ajouter un utilisateur
 | 
				
			||||||
adminUserSettings.usernameInfo=Le nom d’utilisateur ne doit contenir que des lettres et des chiffres, sans espaces ni caractères spéciaux.
 | 
					adminUserSettings.usernameInfo=Le nom d'utilisateur ne peut contenir que des lettres, des chiffres et les caractères spéciaux suivants @._+- ou doit être une adresse e-mail valide.
 | 
				
			||||||
adminUserSettings.roles=Rôles
 | 
					adminUserSettings.roles=Rôles
 | 
				
			||||||
adminUserSettings.role=Rôle
 | 
					adminUserSettings.role=Rôle
 | 
				
			||||||
adminUserSettings.actions=Actions
 | 
					adminUserSettings.actions=Actions
 | 
				
			||||||
@ -182,6 +185,7 @@ adminUserSettings.internalApiUser=Internal API User
 | 
				
			|||||||
adminUserSettings.forceChange=Forcer l’utilisateur à changer son nom d’utilisateur/mot de passe lors de la connexion
 | 
					adminUserSettings.forceChange=Forcer l’utilisateur à changer son nom d’utilisateur/mot de passe lors de la connexion
 | 
				
			||||||
adminUserSettings.submit=Ajouter
 | 
					adminUserSettings.submit=Ajouter
 | 
				
			||||||
adminUserSettings.changeUserRole=Changer le rôle de l'utilisateur
 | 
					adminUserSettings.changeUserRole=Changer le rôle de l'utilisateur
 | 
				
			||||||
 | 
					adminUserSettings.authenticated=Authenticated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#############
 | 
					#############
 | 
				
			||||||
# HOME-PAGE #
 | 
					# HOME-PAGE #
 | 
				
			||||||
@ -801,7 +805,7 @@ pdfOrganiser.placeholder=(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)
 | 
				
			|||||||
#multiTool
 | 
					#multiTool
 | 
				
			||||||
multiTool.title=Outil multifonction PDF
 | 
					multiTool.title=Outil multifonction PDF
 | 
				
			||||||
multiTool.header=Outil multifonction PDF
 | 
					multiTool.header=Outil multifonction PDF
 | 
				
			||||||
multiTool.uploadPrompts=Please Upload PDF
 | 
					multiTool.uploadPrompts=File Name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#view pdf
 | 
					#view pdf
 | 
				
			||||||
viewPdf.title=Visualiser un PDF
 | 
					viewPdf.title=Visualiser un PDF
 | 
				
			||||||
@ -1056,7 +1060,7 @@ licenses.version=Version
 | 
				
			|||||||
licenses.license=Licence
 | 
					licenses.license=Licence
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# error
 | 
					#error
 | 
				
			||||||
error.sorry=Désolé pour ce problème !
 | 
					error.sorry=Désolé pour ce problème !
 | 
				
			||||||
error.needHelp=Besoin d’aide / Vous avez trouvé un problème ?
 | 
					error.needHelp=Besoin d’aide / Vous avez trouvé un problème ?
 | 
				
			||||||
error.contactTip=Si vous avez encore des problèmes, n’hésitez pas à nous contacter pour obtenir de l’aide. Vous pouvez soumettre un ticket sur notre page GitHub ou nous contacter via Discord :
 | 
					error.contactTip=Si vous avez encore des problèmes, n’hésitez pas à nous contacter pour obtenir de l’aide. Vous pouvez soumettre un ticket sur notre page GitHub ou nous contacter via Discord :
 | 
				
			||||||
 | 
				
			|||||||
@ -54,11 +54,13 @@ notAuthenticatedMessage=उपयोगकर्ता प्रमाणित
 | 
				
			|||||||
userNotFoundMessage=उपयोगकर्ता नहीं मिला।
 | 
					userNotFoundMessage=उपयोगकर्ता नहीं मिला।
 | 
				
			||||||
incorrectPasswordMessage=वर्तमान पासवर्ड गलत है।
 | 
					incorrectPasswordMessage=वर्तमान पासवर्ड गलत है।
 | 
				
			||||||
usernameExistsMessage=नया उपयोगकर्ता नाम पहले से मौजूद है।
 | 
					usernameExistsMessage=नया उपयोगकर्ता नाम पहले से मौजूद है।
 | 
				
			||||||
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
 | 
					invalidUsernameMessage=Invalid username, username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
 | 
				
			||||||
deleteCurrentUserMessage=Cannot delete currently logged in user.
 | 
					deleteCurrentUserMessage=Cannot delete currently logged in user.
 | 
				
			||||||
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
 | 
					deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
 | 
				
			||||||
downgradeCurrentUserMessage=मौजूदा यूज़र की भूमिका को डाउनग्रेड नहीं किया जा सकता
 | 
					downgradeCurrentUserMessage=मौजूदा यूज़र की भूमिका को डाउनग्रेड नहीं किया जा सकता
 | 
				
			||||||
downgradeCurrentUserLongMessage=मौजूदा यूज़र की भूमिका को डाउनग्रेड नहीं किया जा सकता। इसलिए, वर्तमान उपयोगकर्ता को नहीं दिखाया जाएगा।
 | 
					downgradeCurrentUserLongMessage=मौजूदा यूज़र की भूमिका को डाउनग्रेड नहीं किया जा सकता। इसलिए, वर्तमान उपयोगकर्ता को नहीं दिखाया जाएगा।
 | 
				
			||||||
 | 
					userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
 | 
				
			||||||
 | 
					userAlreadyExistsWebMessage=The user already exists as an web user.
 | 
				
			||||||
error=Error
 | 
					error=Error
 | 
				
			||||||
oops=Oops!
 | 
					oops=Oops!
 | 
				
			||||||
help=Help
 | 
					help=Help
 | 
				
			||||||
@ -171,7 +173,7 @@ adminUserSettings.header=व्यवस्थापक उपयोगकर्
 | 
				
			|||||||
adminUserSettings.admin=व्यवस्थापक
 | 
					adminUserSettings.admin=व्यवस्थापक
 | 
				
			||||||
adminUserSettings.user=उपयोगकर्ता
 | 
					adminUserSettings.user=उपयोगकर्ता
 | 
				
			||||||
adminUserSettings.addUser=नया उपयोगकर्ता जोड़ें
 | 
					adminUserSettings.addUser=नया उपयोगकर्ता जोड़ें
 | 
				
			||||||
adminUserSettings.usernameInfo=Username must only contain letters and numbers, no spaces or special characters.
 | 
					adminUserSettings.usernameInfo=Username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
 | 
				
			||||||
adminUserSettings.roles=रोल्स
 | 
					adminUserSettings.roles=रोल्स
 | 
				
			||||||
adminUserSettings.role=रोल
 | 
					adminUserSettings.role=रोल
 | 
				
			||||||
adminUserSettings.actions=क्रियाएँ
 | 
					adminUserSettings.actions=क्रियाएँ
 | 
				
			||||||
@ -183,6 +185,7 @@ adminUserSettings.internalApiUser=Internal API User
 | 
				
			|||||||
adminUserSettings.forceChange=उपयोगकर्ता को लॉगिन पर उपयोगकर्ता नाम/पासवर्ड बदलने के लिए मजबूर करें
 | 
					adminUserSettings.forceChange=उपयोगकर्ता को लॉगिन पर उपयोगकर्ता नाम/पासवर्ड बदलने के लिए मजबूर करें
 | 
				
			||||||
adminUserSettings.submit=उपयोगकर्ता को सहेजें
 | 
					adminUserSettings.submit=उपयोगकर्ता को सहेजें
 | 
				
			||||||
adminUserSettings.changeUserRole=यूज़र की भूमिका बदलें
 | 
					adminUserSettings.changeUserRole=यूज़र की भूमिका बदलें
 | 
				
			||||||
 | 
					adminUserSettings.authenticated=Authenticated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#############
 | 
					#############
 | 
				
			||||||
# HOME-PAGE #
 | 
					# HOME-PAGE #
 | 
				
			||||||
@ -802,7 +805,7 @@ pdfOrganiser.placeholder=(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)
 | 
				
			|||||||
#multiTool
 | 
					#multiTool
 | 
				
			||||||
multiTool.title=पीडीएफ मल्टी टूल
 | 
					multiTool.title=पीडीएफ मल्टी टूल
 | 
				
			||||||
multiTool.header=पीडीएफ मल्टी टूल
 | 
					multiTool.header=पीडीएफ मल्टी टूल
 | 
				
			||||||
multiTool.uploadPrompts=Please Upload PDF
 | 
					multiTool.uploadPrompts=File Name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#view pdf
 | 
					#view pdf
 | 
				
			||||||
viewPdf.title=पीडीएफ देखें
 | 
					viewPdf.title=पीडीएफ देखें
 | 
				
			||||||
@ -1057,7 +1060,7 @@ licenses.version=Version
 | 
				
			|||||||
licenses.license=License
 | 
					licenses.license=License
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# error
 | 
					#error
 | 
				
			||||||
error.sorry=Sorry for the issue!
 | 
					error.sorry=Sorry for the issue!
 | 
				
			||||||
error.needHelp=Need help / Found an issue?
 | 
					error.needHelp=Need help / Found an issue?
 | 
				
			||||||
error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
 | 
					error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
 | 
				
			||||||
 | 
				
			|||||||
@ -54,11 +54,13 @@ notAuthenticatedMessage=Felhasználó nincs hitelesítve.
 | 
				
			|||||||
userNotFoundMessage=A felhasználó nem található.
 | 
					userNotFoundMessage=A felhasználó nem található.
 | 
				
			||||||
incorrectPasswordMessage=A jelenlegi jelszó helytelen.
 | 
					incorrectPasswordMessage=A jelenlegi jelszó helytelen.
 | 
				
			||||||
usernameExistsMessage=Az új felhasználónév már létezik.
 | 
					usernameExistsMessage=Az új felhasználónév már létezik.
 | 
				
			||||||
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
 | 
					invalidUsernameMessage=Invalid username, username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
 | 
				
			||||||
deleteCurrentUserMessage=Cannot delete currently logged in user.
 | 
					deleteCurrentUserMessage=Cannot delete currently logged in user.
 | 
				
			||||||
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
 | 
					deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
 | 
				
			||||||
downgradeCurrentUserMessage=A jelenlegi felhasználó szerepkörét nem lehet visszaminősíteni
 | 
					downgradeCurrentUserMessage=A jelenlegi felhasználó szerepkörét nem lehet visszaminősíteni
 | 
				
			||||||
downgradeCurrentUserLongMessage=Az aktuális felhasználó szerepkörét nem lehet visszaminősíteni. Ezért az aktuális felhasználó nem jelenik meg.
 | 
					downgradeCurrentUserLongMessage=Az aktuális felhasználó szerepkörét nem lehet visszaminősíteni. Ezért az aktuális felhasználó nem jelenik meg.
 | 
				
			||||||
 | 
					userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
 | 
				
			||||||
 | 
					userAlreadyExistsWebMessage=The user already exists as an web user.
 | 
				
			||||||
error=Error
 | 
					error=Error
 | 
				
			||||||
oops=Oops!
 | 
					oops=Oops!
 | 
				
			||||||
help=Help
 | 
					help=Help
 | 
				
			||||||
@ -171,7 +173,7 @@ adminUserSettings.header=Adminisztrátori Felhasználói Vezérlési Beállítá
 | 
				
			|||||||
adminUserSettings.admin=Adminisztrátor
 | 
					adminUserSettings.admin=Adminisztrátor
 | 
				
			||||||
adminUserSettings.user=Felhasználó
 | 
					adminUserSettings.user=Felhasználó
 | 
				
			||||||
adminUserSettings.addUser=Új felhasználó hozzáadása
 | 
					adminUserSettings.addUser=Új felhasználó hozzáadása
 | 
				
			||||||
adminUserSettings.usernameInfo=Username must only contain letters and numbers, no spaces or special characters.
 | 
					adminUserSettings.usernameInfo=Username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
 | 
				
			||||||
adminUserSettings.roles=Szerepek
 | 
					adminUserSettings.roles=Szerepek
 | 
				
			||||||
adminUserSettings.role=Szerep
 | 
					adminUserSettings.role=Szerep
 | 
				
			||||||
adminUserSettings.actions=Műveletek
 | 
					adminUserSettings.actions=Műveletek
 | 
				
			||||||
@ -183,6 +185,7 @@ adminUserSettings.internalApiUser=Internal API User
 | 
				
			|||||||
adminUserSettings.forceChange=Kényszerítse a felhasználót a felhasználónév/jelszó megváltoztatására bejelentkezéskor
 | 
					adminUserSettings.forceChange=Kényszerítse a felhasználót a felhasználónév/jelszó megváltoztatására bejelentkezéskor
 | 
				
			||||||
adminUserSettings.submit=Felhasználó mentése
 | 
					adminUserSettings.submit=Felhasználó mentése
 | 
				
			||||||
adminUserSettings.changeUserRole=Felhasználó szerepkörének módosítása
 | 
					adminUserSettings.changeUserRole=Felhasználó szerepkörének módosítása
 | 
				
			||||||
 | 
					adminUserSettings.authenticated=Authenticated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#############
 | 
					#############
 | 
				
			||||||
# HOME-PAGE #
 | 
					# HOME-PAGE #
 | 
				
			||||||
@ -802,7 +805,7 @@ pdfOrganiser.placeholder=(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)
 | 
				
			|||||||
#multiTool
 | 
					#multiTool
 | 
				
			||||||
multiTool.title=PDF többfunkciós eszköz
 | 
					multiTool.title=PDF többfunkciós eszköz
 | 
				
			||||||
multiTool.header=PDF többfunkciós eszköz
 | 
					multiTool.header=PDF többfunkciós eszköz
 | 
				
			||||||
multiTool.uploadPrompts=Please Upload PDF
 | 
					multiTool.uploadPrompts=File Name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#view pdf
 | 
					#view pdf
 | 
				
			||||||
viewPdf.title=PDF megtekintése
 | 
					viewPdf.title=PDF megtekintése
 | 
				
			||||||
@ -1057,7 +1060,7 @@ licenses.version=Version
 | 
				
			|||||||
licenses.license=License
 | 
					licenses.license=License
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# error
 | 
					#error
 | 
				
			||||||
error.sorry=Sorry for the issue!
 | 
					error.sorry=Sorry for the issue!
 | 
				
			||||||
error.needHelp=Need help / Found an issue?
 | 
					error.needHelp=Need help / Found an issue?
 | 
				
			||||||
error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
 | 
					error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
 | 
				
			||||||
 | 
				
			|||||||
@ -54,11 +54,13 @@ notAuthenticatedMessage=Pengguna tidak ter-autentikasi.
 | 
				
			|||||||
userNotFoundMessage=Pengguna tidak ditemukan.
 | 
					userNotFoundMessage=Pengguna tidak ditemukan.
 | 
				
			||||||
incorrectPasswordMessage=Kata sandi saat ini salah.
 | 
					incorrectPasswordMessage=Kata sandi saat ini salah.
 | 
				
			||||||
usernameExistsMessage=Nama pengguna baru sudah ada.
 | 
					usernameExistsMessage=Nama pengguna baru sudah ada.
 | 
				
			||||||
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
 | 
					invalidUsernameMessage=Invalid username, username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
 | 
				
			||||||
deleteCurrentUserMessage=Cannot delete currently logged in user.
 | 
					deleteCurrentUserMessage=Cannot delete currently logged in user.
 | 
				
			||||||
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
 | 
					deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
 | 
				
			||||||
downgradeCurrentUserMessage=Tidak dapat menurunkan peran pengguna saat ini
 | 
					downgradeCurrentUserMessage=Tidak dapat menurunkan peran pengguna saat ini
 | 
				
			||||||
downgradeCurrentUserLongMessage=Tidak dapat menurunkan peran pengguna saat ini. Oleh karena itu, pengguna saat ini tidak akan ditampilkan.
 | 
					downgradeCurrentUserLongMessage=Tidak dapat menurunkan peran pengguna saat ini. Oleh karena itu, pengguna saat ini tidak akan ditampilkan.
 | 
				
			||||||
 | 
					userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
 | 
				
			||||||
 | 
					userAlreadyExistsWebMessage=The user already exists as an web user.
 | 
				
			||||||
error=Error
 | 
					error=Error
 | 
				
			||||||
oops=Oops!
 | 
					oops=Oops!
 | 
				
			||||||
help=Help
 | 
					help=Help
 | 
				
			||||||
@ -171,7 +173,7 @@ adminUserSettings.header=Pengaturan Kontrol Admin
 | 
				
			|||||||
adminUserSettings.admin=Admin
 | 
					adminUserSettings.admin=Admin
 | 
				
			||||||
adminUserSettings.user=Pengguna
 | 
					adminUserSettings.user=Pengguna
 | 
				
			||||||
adminUserSettings.addUser=Tambahkan Pengguna Baru
 | 
					adminUserSettings.addUser=Tambahkan Pengguna Baru
 | 
				
			||||||
adminUserSettings.usernameInfo=Username must only contain letters and numbers, no spaces or special characters.
 | 
					adminUserSettings.usernameInfo=Username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
 | 
				
			||||||
adminUserSettings.roles=Peran
 | 
					adminUserSettings.roles=Peran
 | 
				
			||||||
adminUserSettings.role=Peran
 | 
					adminUserSettings.role=Peran
 | 
				
			||||||
adminUserSettings.actions=Tindakan
 | 
					adminUserSettings.actions=Tindakan
 | 
				
			||||||
@ -183,6 +185,7 @@ adminUserSettings.internalApiUser=Internal API User
 | 
				
			|||||||
adminUserSettings.forceChange=Memaksa pengguna untuk mengubah nama pengguna/kata sandi saat masuk
 | 
					adminUserSettings.forceChange=Memaksa pengguna untuk mengubah nama pengguna/kata sandi saat masuk
 | 
				
			||||||
adminUserSettings.submit=Simpan Pengguna
 | 
					adminUserSettings.submit=Simpan Pengguna
 | 
				
			||||||
adminUserSettings.changeUserRole=Ubah Peran Pengguna
 | 
					adminUserSettings.changeUserRole=Ubah Peran Pengguna
 | 
				
			||||||
 | 
					adminUserSettings.authenticated=Authenticated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#############
 | 
					#############
 | 
				
			||||||
# HOME-PAGE #
 | 
					# HOME-PAGE #
 | 
				
			||||||
@ -802,7 +805,7 @@ pdfOrganiser.placeholder=(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)
 | 
				
			|||||||
#multiTool
 | 
					#multiTool
 | 
				
			||||||
multiTool.title=Alat Multi PDF
 | 
					multiTool.title=Alat Multi PDF
 | 
				
			||||||
multiTool.header=Alat Multi PDF
 | 
					multiTool.header=Alat Multi PDF
 | 
				
			||||||
multiTool.uploadPrompts=Please Upload PDF
 | 
					multiTool.uploadPrompts=File Name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#view pdf
 | 
					#view pdf
 | 
				
			||||||
viewPdf.title=Lihat PDF
 | 
					viewPdf.title=Lihat PDF
 | 
				
			||||||
@ -1057,7 +1060,7 @@ licenses.version=Version
 | 
				
			|||||||
licenses.license=License
 | 
					licenses.license=License
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# error
 | 
					#error
 | 
				
			||||||
error.sorry=Sorry for the issue!
 | 
					error.sorry=Sorry for the issue!
 | 
				
			||||||
error.needHelp=Need help / Found an issue?
 | 
					error.needHelp=Need help / Found an issue?
 | 
				
			||||||
error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
 | 
					error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
 | 
				
			||||||
 | 
				
			|||||||
@ -54,11 +54,13 @@ notAuthenticatedMessage=Utente non autenticato.
 | 
				
			|||||||
userNotFoundMessage=Utente non trovato.
 | 
					userNotFoundMessage=Utente non trovato.
 | 
				
			||||||
incorrectPasswordMessage=La password attuale non è corretta.
 | 
					incorrectPasswordMessage=La password attuale non è corretta.
 | 
				
			||||||
usernameExistsMessage=Il nuovo nome utente esiste già.
 | 
					usernameExistsMessage=Il nuovo nome utente esiste già.
 | 
				
			||||||
invalidUsernameMessage=Nome utente non valido, il nome utente deve contenere solo caratteri alfabetici e numeri.
 | 
					invalidUsernameMessage=Nome utente non valido, il nome utente può contenere solo lettere, numeri e i seguenti caratteri speciali @._+- o deve essere un indirizzo email valido.
 | 
				
			||||||
deleteCurrentUserMessage=Impossibile eliminare l'utente attualmente connesso.
 | 
					deleteCurrentUserMessage=Impossibile eliminare l'utente attualmente connesso.
 | 
				
			||||||
deleteUsernameExistsMessage=Il nome utente non esiste e non può essere eliminato.
 | 
					deleteUsernameExistsMessage=Il nome utente non esiste e non può essere eliminato.
 | 
				
			||||||
downgradeCurrentUserMessage=Impossibile declassare il ruolo dell'utente corrente
 | 
					downgradeCurrentUserMessage=Impossibile declassare il ruolo dell'utente corrente
 | 
				
			||||||
downgradeCurrentUserLongMessage=Impossibile declassare il ruolo dell'utente corrente. Pertanto, l'utente corrente non verrà visualizzato.
 | 
					downgradeCurrentUserLongMessage=Impossibile declassare il ruolo dell'utente corrente. Pertanto, l'utente corrente non verrà visualizzato.
 | 
				
			||||||
 | 
					userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
 | 
				
			||||||
 | 
					userAlreadyExistsWebMessage=The user already exists as an web user.
 | 
				
			||||||
error=Errore
 | 
					error=Errore
 | 
				
			||||||
oops=Oops!
 | 
					oops=Oops!
 | 
				
			||||||
help=Aiuto
 | 
					help=Aiuto
 | 
				
			||||||
@ -171,7 +173,7 @@ adminUserSettings.header=Impostazioni di controllo utente amministratore
 | 
				
			|||||||
adminUserSettings.admin=Amministratore
 | 
					adminUserSettings.admin=Amministratore
 | 
				
			||||||
adminUserSettings.user=Utente
 | 
					adminUserSettings.user=Utente
 | 
				
			||||||
adminUserSettings.addUser=Aggiungi un nuovo Utente
 | 
					adminUserSettings.addUser=Aggiungi un nuovo Utente
 | 
				
			||||||
adminUserSettings.usernameInfo=Il nome utente deve contenere solo lettere e numeri, senza spazi o caratteri speciali.
 | 
					adminUserSettings.usernameInfo=Il nome utente può contenere solo lettere, numeri e i seguenti caratteri speciali @._+- oppure deve essere un indirizzo email valido.
 | 
				
			||||||
adminUserSettings.roles=Ruoli
 | 
					adminUserSettings.roles=Ruoli
 | 
				
			||||||
adminUserSettings.role=Ruolo
 | 
					adminUserSettings.role=Ruolo
 | 
				
			||||||
adminUserSettings.actions=Azioni
 | 
					adminUserSettings.actions=Azioni
 | 
				
			||||||
@ -183,6 +185,7 @@ adminUserSettings.internalApiUser=API utente interna
 | 
				
			|||||||
adminUserSettings.forceChange=Forza l'utente a cambiare nome username/password all'accesso
 | 
					adminUserSettings.forceChange=Forza l'utente a cambiare nome username/password all'accesso
 | 
				
			||||||
adminUserSettings.submit=Salva utente
 | 
					adminUserSettings.submit=Salva utente
 | 
				
			||||||
adminUserSettings.changeUserRole=Cambia il ruolo dell'utente
 | 
					adminUserSettings.changeUserRole=Cambia il ruolo dell'utente
 | 
				
			||||||
 | 
					adminUserSettings.authenticated=Authenticated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#############
 | 
					#############
 | 
				
			||||||
# HOME-PAGE #
 | 
					# HOME-PAGE #
 | 
				
			||||||
@ -802,7 +805,7 @@ pdfOrganiser.placeholder=(ad es. 1,3,2 o 4-8,2,10-12 o 2n-1)
 | 
				
			|||||||
#multiTool
 | 
					#multiTool
 | 
				
			||||||
multiTool.title=Multifunzione PDF
 | 
					multiTool.title=Multifunzione PDF
 | 
				
			||||||
multiTool.header=Multifunzione PDF
 | 
					multiTool.header=Multifunzione PDF
 | 
				
			||||||
multiTool.uploadPrompts=Caricare il PDF
 | 
					multiTool.uploadPrompts=File Name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#view pdf
 | 
					#view pdf
 | 
				
			||||||
viewPdf.title=Visualizza PDF
 | 
					viewPdf.title=Visualizza PDF
 | 
				
			||||||
@ -1057,7 +1060,7 @@ licenses.version=Versione
 | 
				
			|||||||
licenses.license=Licenza
 | 
					licenses.license=Licenza
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# error
 | 
					#error
 | 
				
			||||||
error.sorry=Ci scusiamo per il problema!
 | 
					error.sorry=Ci scusiamo per il problema!
 | 
				
			||||||
error.needHelp=Hai bisogno di aiuto / trovato un problema?
 | 
					error.needHelp=Hai bisogno di aiuto / trovato un problema?
 | 
				
			||||||
error.contactTip=Se i problemi persistono, non esitare a contattarci per chiedere aiuto. Puoi inviare un ticket sulla nostra pagina GitHub o contattarci tramite Discord:
 | 
					error.contactTip=Se i problemi persistono, non esitare a contattarci per chiedere aiuto. Puoi inviare un ticket sulla nostra pagina GitHub o contattarci tramite Discord:
 | 
				
			||||||
 | 
				
			|||||||
@ -54,11 +54,13 @@ notAuthenticatedMessage=ユーザーが認証されていません。
 | 
				
			|||||||
userNotFoundMessage=ユーザーが見つかりません。
 | 
					userNotFoundMessage=ユーザーが見つかりません。
 | 
				
			||||||
incorrectPasswordMessage=現在のパスワードが正しくありません。
 | 
					incorrectPasswordMessage=現在のパスワードが正しくありません。
 | 
				
			||||||
usernameExistsMessage=新しいユーザー名はすでに存在します。
 | 
					usernameExistsMessage=新しいユーザー名はすでに存在します。
 | 
				
			||||||
invalidUsernameMessage=ユーザー名が無効です。ユーザー名にはアルファベットと数字のみを使用してください。
 | 
					invalidUsernameMessage=ユーザー名が無効です。ユーザー名には文字、数字、およびそれに続く特殊文字 @._+- のみを含めることができます。または、有効な電子メール アドレスである必要があります。
 | 
				
			||||||
deleteCurrentUserMessage=現在ログインしているユーザーは削除できません。
 | 
					deleteCurrentUserMessage=現在ログインしているユーザーは削除できません。
 | 
				
			||||||
deleteUsernameExistsMessage=そのユーザー名は存在しないため削除できません。
 | 
					deleteUsernameExistsMessage=そのユーザー名は存在しないため削除できません。
 | 
				
			||||||
downgradeCurrentUserMessage=現在のユーザーの役割をダウングレードできません
 | 
					downgradeCurrentUserMessage=現在のユーザーの役割をダウングレードできません
 | 
				
			||||||
downgradeCurrentUserLongMessage=現在のユーザーの役割をダウングレードできません。したがって、現在のユーザーは表示されません。
 | 
					downgradeCurrentUserLongMessage=現在のユーザーの役割をダウングレードできません。したがって、現在のユーザーは表示されません。
 | 
				
			||||||
 | 
					userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
 | 
				
			||||||
 | 
					userAlreadyExistsWebMessage=The user already exists as an web user.
 | 
				
			||||||
error=エラー
 | 
					error=エラー
 | 
				
			||||||
oops=おっと!
 | 
					oops=おっと!
 | 
				
			||||||
help=ヘルプ
 | 
					help=ヘルプ
 | 
				
			||||||
@ -171,7 +173,7 @@ adminUserSettings.header=管理者ユーザー制御設定
 | 
				
			|||||||
adminUserSettings.admin=管理者
 | 
					adminUserSettings.admin=管理者
 | 
				
			||||||
adminUserSettings.user=ユーザー
 | 
					adminUserSettings.user=ユーザー
 | 
				
			||||||
adminUserSettings.addUser=新しいユーザを追加
 | 
					adminUserSettings.addUser=新しいユーザを追加
 | 
				
			||||||
adminUserSettings.usernameInfo=ユーザー名には文字と数字のみが使用でき、スペースや特殊文字は使用できません。
 | 
					adminUserSettings.usernameInfo=ユーザー名には、文字、数字、および次の特殊文字 @._+- のみを含めることができます。または、有効な電子メール アドレスである必要があります。
 | 
				
			||||||
adminUserSettings.roles=役割
 | 
					adminUserSettings.roles=役割
 | 
				
			||||||
adminUserSettings.role=役割
 | 
					adminUserSettings.role=役割
 | 
				
			||||||
adminUserSettings.actions=アクション
 | 
					adminUserSettings.actions=アクション
 | 
				
			||||||
@ -183,6 +185,7 @@ adminUserSettings.internalApiUser=内部APIユーザー
 | 
				
			|||||||
adminUserSettings.forceChange=ログイン時にユーザー名/パスワードを強制的に変更する
 | 
					adminUserSettings.forceChange=ログイン時にユーザー名/パスワードを強制的に変更する
 | 
				
			||||||
adminUserSettings.submit=ユーザーの保存
 | 
					adminUserSettings.submit=ユーザーの保存
 | 
				
			||||||
adminUserSettings.changeUserRole=ユーザーの役割を変更する
 | 
					adminUserSettings.changeUserRole=ユーザーの役割を変更する
 | 
				
			||||||
 | 
					adminUserSettings.authenticated=Authenticated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#############
 | 
					#############
 | 
				
			||||||
# HOME-PAGE #
 | 
					# HOME-PAGE #
 | 
				
			||||||
@ -802,7 +805,7 @@ pdfOrganiser.placeholder=(例:1,3,2または4-8,2,10-12または2n-1)
 | 
				
			|||||||
#multiTool
 | 
					#multiTool
 | 
				
			||||||
multiTool.title=PDFマルチツール
 | 
					multiTool.title=PDFマルチツール
 | 
				
			||||||
multiTool.header=PDFマルチツール
 | 
					multiTool.header=PDFマルチツール
 | 
				
			||||||
multiTool.uploadPrompts=Please Upload PDF
 | 
					multiTool.uploadPrompts=File Name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#view pdf
 | 
					#view pdf
 | 
				
			||||||
viewPdf.title=PDFを表示
 | 
					viewPdf.title=PDFを表示
 | 
				
			||||||
@ -1057,7 +1060,7 @@ licenses.version=バージョン
 | 
				
			|||||||
licenses.license=ライセンス
 | 
					licenses.license=ライセンス
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# error
 | 
					#error
 | 
				
			||||||
error.sorry=問題が発生したことをお詫び申し上げます!
 | 
					error.sorry=問題が発生したことをお詫び申し上げます!
 | 
				
			||||||
error.needHelp=助けが必要/問題が見つかりましたか?
 | 
					error.needHelp=助けが必要/問題が見つかりましたか?
 | 
				
			||||||
error.contactTip=まだ問題が解決していない場合は、お手数ですが、GitHubページでチケットを提出するか、Discordで私たちに連絡してください:
 | 
					error.contactTip=まだ問題が解決していない場合は、お手数ですが、GitHubページでチケットを提出するか、Discordで私たちに連絡してください:
 | 
				
			||||||
 | 
				
			|||||||
@ -54,13 +54,13 @@ notAuthenticatedMessage=사용자가 인증되지 않았습니다.
 | 
				
			|||||||
userNotFoundMessage=사용자를 찾을 수 없습니다.
 | 
					userNotFoundMessage=사용자를 찾을 수 없습니다.
 | 
				
			||||||
incorrectPasswordMessage=현재 비밀번호가 틀립니다.
 | 
					incorrectPasswordMessage=현재 비밀번호가 틀립니다.
 | 
				
			||||||
usernameExistsMessage=새 사용자명이 이미 존재합니다.
 | 
					usernameExistsMessage=새 사용자명이 이미 존재합니다.
 | 
				
			||||||
invalidUsernameMessage=사용자 이름이 잘못되었습니다. 사용자 이름에는 알파벳 문자와 숫자만 포함되어야 합니다.
 | 
					invalidUsernameMessage=잘못된 사용자 이름입니다. 사용자 이름에는 문자, 숫자 및 다음 특수 문자(@._+-)만 포함할 수 있거나 유효한 이메일 주소여야 합니다.
 | 
				
			||||||
deleteCurrentUserMessage=현재 로그인한 사용자를 삭제할 수 없습니다.
 | 
					deleteCurrentUserMessage=현재 로그인한 사용자를 삭제할 수 없습니다.
 | 
				
			||||||
deleteUsernameExistsMessage=사용자 이름이 존재하지 않으며 삭제할 수 없습니다.
 | 
					deleteUsernameExistsMessage=사용자 이름이 존재하지 않으며 삭제할 수 없습니다.
 | 
				
			||||||
 | 
					 | 
				
			||||||
info=Info
 | 
					 | 
				
			||||||
downgradeCurrentUserMessage=현재 사용자의 역할을 다운그레이드할 수 없습니다
 | 
					downgradeCurrentUserMessage=현재 사용자의 역할을 다운그레이드할 수 없습니다
 | 
				
			||||||
downgradeCurrentUserLongMessage=현재 사용자의 역할을 다운그레이드할 수 없습니다. 따라서 현재 사용자는 표시되지 않습니다.
 | 
					downgradeCurrentUserLongMessage=현재 사용자의 역할을 다운그레이드할 수 없습니다. 따라서 현재 사용자는 표시되지 않습니다.
 | 
				
			||||||
 | 
					userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
 | 
				
			||||||
 | 
					userAlreadyExistsWebMessage=The user already exists as an web user.
 | 
				
			||||||
error=오류
 | 
					error=오류
 | 
				
			||||||
oops=어머나!
 | 
					oops=어머나!
 | 
				
			||||||
help=도움말
 | 
					help=도움말
 | 
				
			||||||
@ -71,6 +71,7 @@ visitGithub=GitHub 저장소 방문하기
 | 
				
			|||||||
donate=기부하기
 | 
					donate=기부하기
 | 
				
			||||||
color=색상
 | 
					color=색상
 | 
				
			||||||
sponsor=스폰서
 | 
					sponsor=스폰서
 | 
				
			||||||
 | 
					info=Info
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -172,7 +173,7 @@ adminUserSettings.header=사용자 관리
 | 
				
			|||||||
adminUserSettings.admin=관리자
 | 
					adminUserSettings.admin=관리자
 | 
				
			||||||
adminUserSettings.user=사용자
 | 
					adminUserSettings.user=사용자
 | 
				
			||||||
adminUserSettings.addUser=새 사용자 추가
 | 
					adminUserSettings.addUser=새 사용자 추가
 | 
				
			||||||
adminUserSettings.usernameInfo=사용자 이름은 문자와 숫자만 포함해야 하며 공백이나 특수 문자는 포함할 수 없습니다.
 | 
					adminUserSettings.usernameInfo=사용자 이름은 문자, 숫자, 특수 문자 @._+-만 포함할 수 있으며 유효한 이메일 주소여야 합니다.
 | 
				
			||||||
adminUserSettings.roles=역할
 | 
					adminUserSettings.roles=역할
 | 
				
			||||||
adminUserSettings.role=역할
 | 
					adminUserSettings.role=역할
 | 
				
			||||||
adminUserSettings.actions=동작
 | 
					adminUserSettings.actions=동작
 | 
				
			||||||
@ -184,6 +185,7 @@ adminUserSettings.internalApiUser=내부 API 사용자
 | 
				
			|||||||
adminUserSettings.forceChange=다음 로그인 때 사용자명과 비밀번호를 변경하도록 강제
 | 
					adminUserSettings.forceChange=다음 로그인 때 사용자명과 비밀번호를 변경하도록 강제
 | 
				
			||||||
adminUserSettings.submit=사용자 저장
 | 
					adminUserSettings.submit=사용자 저장
 | 
				
			||||||
adminUserSettings.changeUserRole=사용자의 역할 변경
 | 
					adminUserSettings.changeUserRole=사용자의 역할 변경
 | 
				
			||||||
 | 
					adminUserSettings.authenticated=Authenticated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#############
 | 
					#############
 | 
				
			||||||
# HOME-PAGE #
 | 
					# HOME-PAGE #
 | 
				
			||||||
@ -803,7 +805,7 @@ pdfOrganiser.placeholder=(예: 1,3,2 또는 4-8,2,10-12 또는 2n-1)
 | 
				
			|||||||
#multiTool
 | 
					#multiTool
 | 
				
			||||||
multiTool.title=PDF 멀티툴
 | 
					multiTool.title=PDF 멀티툴
 | 
				
			||||||
multiTool.header=PDF 멀티툴
 | 
					multiTool.header=PDF 멀티툴
 | 
				
			||||||
multiTool.uploadPrompts=PDF를 업로드하십시오
 | 
					multiTool.uploadPrompts=File Name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#view pdf
 | 
					#view pdf
 | 
				
			||||||
viewPdf.title=PDF 뷰어
 | 
					viewPdf.title=PDF 뷰어
 | 
				
			||||||
@ -1058,7 +1060,7 @@ licenses.version=버전
 | 
				
			|||||||
licenses.license=라이센스
 | 
					licenses.license=라이센스
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# error
 | 
					#error
 | 
				
			||||||
error.sorry=문제를 끼친 점 죄송합니다!
 | 
					error.sorry=문제를 끼친 점 죄송합니다!
 | 
				
			||||||
error.needHelp=도움이 필요하신가요 / 문제가 있으신가요?
 | 
					error.needHelp=도움이 필요하신가요 / 문제가 있으신가요?
 | 
				
			||||||
error.contactTip=여전히 문제가 해결되지 않는다면 망설이지 마시고 도움을 요청하십시오. GitHub 페이지에서 티켓을 제출하거나 Discord를 통해 우리에게 연락하실 수 있습니다:
 | 
					error.contactTip=여전히 문제가 해결되지 않는다면 망설이지 마시고 도움을 요청하십시오. GitHub 페이지에서 티켓을 제출하거나 Discord를 통해 우리에게 연락하실 수 있습니다:
 | 
				
			||||||
 | 
				
			|||||||
@ -54,11 +54,13 @@ notAuthenticatedMessage=Gebruiker niet ingelogd.
 | 
				
			|||||||
userNotFoundMessage=Gebruiker niet gevonden.
 | 
					userNotFoundMessage=Gebruiker niet gevonden.
 | 
				
			||||||
incorrectPasswordMessage=Huidige wachtwoord is onjuist.
 | 
					incorrectPasswordMessage=Huidige wachtwoord is onjuist.
 | 
				
			||||||
usernameExistsMessage=Nieuwe gebruikersnaam bestaat al.
 | 
					usernameExistsMessage=Nieuwe gebruikersnaam bestaat al.
 | 
				
			||||||
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
 | 
					invalidUsernameMessage=Invalid username, username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
 | 
				
			||||||
deleteCurrentUserMessage=Cannot delete currently logged in user.
 | 
					deleteCurrentUserMessage=Cannot delete currently logged in user.
 | 
				
			||||||
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
 | 
					deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
 | 
				
			||||||
downgradeCurrentUserMessage=Kan de rol van de huidige gebruiker niet downgraden
 | 
					downgradeCurrentUserMessage=Kan de rol van de huidige gebruiker niet downgraden
 | 
				
			||||||
downgradeCurrentUserLongMessage=Kan de rol van de huidige gebruiker niet downgraden. Huidige gebruiker wordt dus niet weergegeven.
 | 
					downgradeCurrentUserLongMessage=Kan de rol van de huidige gebruiker niet downgraden. Huidige gebruiker wordt dus niet weergegeven.
 | 
				
			||||||
 | 
					userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
 | 
				
			||||||
 | 
					userAlreadyExistsWebMessage=The user already exists as an web user.
 | 
				
			||||||
error=Error
 | 
					error=Error
 | 
				
			||||||
oops=Oops!
 | 
					oops=Oops!
 | 
				
			||||||
help=Help
 | 
					help=Help
 | 
				
			||||||
@ -171,7 +173,7 @@ adminUserSettings.header=Beheer gebruikers
 | 
				
			|||||||
adminUserSettings.admin=Beheerder
 | 
					adminUserSettings.admin=Beheerder
 | 
				
			||||||
adminUserSettings.user=Gebruiker
 | 
					adminUserSettings.user=Gebruiker
 | 
				
			||||||
adminUserSettings.addUser=Voeg nieuwe gebruiker toe
 | 
					adminUserSettings.addUser=Voeg nieuwe gebruiker toe
 | 
				
			||||||
adminUserSettings.usernameInfo=Username must only contain letters and numbers, no spaces or special characters.
 | 
					adminUserSettings.usernameInfo=Username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
 | 
				
			||||||
adminUserSettings.roles=Rollen
 | 
					adminUserSettings.roles=Rollen
 | 
				
			||||||
adminUserSettings.role=Rol
 | 
					adminUserSettings.role=Rol
 | 
				
			||||||
adminUserSettings.actions=Acties
 | 
					adminUserSettings.actions=Acties
 | 
				
			||||||
@ -183,6 +185,7 @@ adminUserSettings.internalApiUser=Internal API User
 | 
				
			|||||||
adminUserSettings.forceChange=Forceer gebruiker om gebruikersnaam/wachtwoord te wijzigen bij inloggen
 | 
					adminUserSettings.forceChange=Forceer gebruiker om gebruikersnaam/wachtwoord te wijzigen bij inloggen
 | 
				
			||||||
adminUserSettings.submit=Gebruiker opslaan
 | 
					adminUserSettings.submit=Gebruiker opslaan
 | 
				
			||||||
adminUserSettings.changeUserRole=De rol van de gebruiker wijzigen
 | 
					adminUserSettings.changeUserRole=De rol van de gebruiker wijzigen
 | 
				
			||||||
 | 
					adminUserSettings.authenticated=Authenticated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#############
 | 
					#############
 | 
				
			||||||
# HOME-PAGE #
 | 
					# HOME-PAGE #
 | 
				
			||||||
@ -802,7 +805,7 @@ pdfOrganiser.placeholder=(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)
 | 
				
			|||||||
#multiTool
 | 
					#multiTool
 | 
				
			||||||
multiTool.title=PDF Multitool
 | 
					multiTool.title=PDF Multitool
 | 
				
			||||||
multiTool.header=PDF Multitool
 | 
					multiTool.header=PDF Multitool
 | 
				
			||||||
multiTool.uploadPrompts=Please Upload PDF
 | 
					multiTool.uploadPrompts=File Name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#view pdf
 | 
					#view pdf
 | 
				
			||||||
viewPdf.title=PDF bekijken
 | 
					viewPdf.title=PDF bekijken
 | 
				
			||||||
@ -1057,7 +1060,7 @@ licenses.version=Versie
 | 
				
			|||||||
licenses.license=Licentie
 | 
					licenses.license=Licentie
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# error
 | 
					#error
 | 
				
			||||||
error.sorry=Sorry for the issue!
 | 
					error.sorry=Sorry for the issue!
 | 
				
			||||||
error.needHelp=Need help / Found an issue?
 | 
					error.needHelp=Need help / Found an issue?
 | 
				
			||||||
error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
 | 
					error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
 | 
				
			||||||
 | 
				
			|||||||
@ -54,11 +54,13 @@ notAuthenticatedMessage=User not authenticated.
 | 
				
			|||||||
userNotFoundMessage=User not found.
 | 
					userNotFoundMessage=User not found.
 | 
				
			||||||
incorrectPasswordMessage=Current password is incorrect.
 | 
					incorrectPasswordMessage=Current password is incorrect.
 | 
				
			||||||
usernameExistsMessage=New Username already exists.
 | 
					usernameExistsMessage=New Username already exists.
 | 
				
			||||||
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
 | 
					invalidUsernameMessage=Invalid username, username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
 | 
				
			||||||
deleteCurrentUserMessage=Cannot delete currently logged in user.
 | 
					deleteCurrentUserMessage=Cannot delete currently logged in user.
 | 
				
			||||||
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
 | 
					deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
 | 
				
			||||||
downgradeCurrentUserMessage=Nie można obniżyć roli bieżącego użytkownika
 | 
					downgradeCurrentUserMessage=Nie można obniżyć roli bieżącego użytkownika
 | 
				
			||||||
downgradeCurrentUserLongMessage=Nie można obniżyć roli bieżącego użytkownika. W związku z tym bieżący użytkownik nie zostanie wyświetlony.
 | 
					downgradeCurrentUserLongMessage=Nie można obniżyć roli bieżącego użytkownika. W związku z tym bieżący użytkownik nie zostanie wyświetlony.
 | 
				
			||||||
 | 
					userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
 | 
				
			||||||
 | 
					userAlreadyExistsWebMessage=The user already exists as an web user.
 | 
				
			||||||
error=Error
 | 
					error=Error
 | 
				
			||||||
oops=Oops!
 | 
					oops=Oops!
 | 
				
			||||||
help=Help
 | 
					help=Help
 | 
				
			||||||
@ -171,7 +173,7 @@ adminUserSettings.header=Admin User Control Settings
 | 
				
			|||||||
adminUserSettings.admin=Admin
 | 
					adminUserSettings.admin=Admin
 | 
				
			||||||
adminUserSettings.user=User
 | 
					adminUserSettings.user=User
 | 
				
			||||||
adminUserSettings.addUser=Add New User
 | 
					adminUserSettings.addUser=Add New User
 | 
				
			||||||
adminUserSettings.usernameInfo=Username must only contain letters and numbers, no spaces or special characters.
 | 
					adminUserSettings.usernameInfo=Username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
 | 
				
			||||||
adminUserSettings.roles=Roles
 | 
					adminUserSettings.roles=Roles
 | 
				
			||||||
adminUserSettings.role=Role
 | 
					adminUserSettings.role=Role
 | 
				
			||||||
adminUserSettings.actions=Actions
 | 
					adminUserSettings.actions=Actions
 | 
				
			||||||
@ -183,6 +185,7 @@ adminUserSettings.internalApiUser=Internal API User
 | 
				
			|||||||
adminUserSettings.forceChange=Force user to change password on login
 | 
					adminUserSettings.forceChange=Force user to change password on login
 | 
				
			||||||
adminUserSettings.submit=Save User
 | 
					adminUserSettings.submit=Save User
 | 
				
			||||||
adminUserSettings.changeUserRole=Zmień rolę użytkownika
 | 
					adminUserSettings.changeUserRole=Zmień rolę użytkownika
 | 
				
			||||||
 | 
					adminUserSettings.authenticated=Authenticated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#############
 | 
					#############
 | 
				
			||||||
# HOME-PAGE #
 | 
					# HOME-PAGE #
 | 
				
			||||||
@ -802,7 +805,7 @@ pdfOrganiser.placeholder=(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)
 | 
				
			|||||||
#multiTool
 | 
					#multiTool
 | 
				
			||||||
multiTool.title=Multi narzędzie PDF
 | 
					multiTool.title=Multi narzędzie PDF
 | 
				
			||||||
multiTool.header=Multi narzędzie PDF
 | 
					multiTool.header=Multi narzędzie PDF
 | 
				
			||||||
multiTool.uploadPrompts=Please Upload PDF
 | 
					multiTool.uploadPrompts=File Name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#view pdf
 | 
					#view pdf
 | 
				
			||||||
viewPdf.title=View PDF
 | 
					viewPdf.title=View PDF
 | 
				
			||||||
@ -1057,7 +1060,7 @@ licenses.version=Version
 | 
				
			|||||||
licenses.license=License
 | 
					licenses.license=License
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# error
 | 
					#error
 | 
				
			||||||
error.sorry=Sorry for the issue!
 | 
					error.sorry=Sorry for the issue!
 | 
				
			||||||
error.needHelp=Need help / Found an issue?
 | 
					error.needHelp=Need help / Found an issue?
 | 
				
			||||||
error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
 | 
					error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
 | 
				
			||||||
 | 
				
			|||||||
@ -54,11 +54,13 @@ notAuthenticatedMessage=User not authenticated.
 | 
				
			|||||||
userNotFoundMessage=User not found.
 | 
					userNotFoundMessage=User not found.
 | 
				
			||||||
incorrectPasswordMessage=Current password is incorrect.
 | 
					incorrectPasswordMessage=Current password is incorrect.
 | 
				
			||||||
usernameExistsMessage=New Username already exists.
 | 
					usernameExistsMessage=New Username already exists.
 | 
				
			||||||
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
 | 
					invalidUsernameMessage=Invalid username, username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
 | 
				
			||||||
deleteCurrentUserMessage=Cannot delete currently logged in user.
 | 
					deleteCurrentUserMessage=Cannot delete currently logged in user.
 | 
				
			||||||
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
 | 
					deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
 | 
				
			||||||
downgradeCurrentUserMessage=Não é possível fazer downgrade da função do usuário atual
 | 
					downgradeCurrentUserMessage=Não é possível fazer downgrade da função do usuário atual
 | 
				
			||||||
downgradeCurrentUserLongMessage=Não é possível fazer downgrade da função do usuário atual. Portanto, o usuário atual não será mostrado.
 | 
					downgradeCurrentUserLongMessage=Não é possível fazer downgrade da função do usuário atual. Portanto, o usuário atual não será mostrado.
 | 
				
			||||||
 | 
					userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
 | 
				
			||||||
 | 
					userAlreadyExistsWebMessage=The user already exists as an web user.
 | 
				
			||||||
error=Error
 | 
					error=Error
 | 
				
			||||||
oops=Oops!
 | 
					oops=Oops!
 | 
				
			||||||
help=Help
 | 
					help=Help
 | 
				
			||||||
@ -171,7 +173,7 @@ adminUserSettings.header=Admin User Control Settings
 | 
				
			|||||||
adminUserSettings.admin=Admin
 | 
					adminUserSettings.admin=Admin
 | 
				
			||||||
adminUserSettings.user=User
 | 
					adminUserSettings.user=User
 | 
				
			||||||
adminUserSettings.addUser=Add New User
 | 
					adminUserSettings.addUser=Add New User
 | 
				
			||||||
adminUserSettings.usernameInfo=Username must only contain letters and numbers, no spaces or special characters.
 | 
					adminUserSettings.usernameInfo=Username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
 | 
				
			||||||
adminUserSettings.roles=Roles
 | 
					adminUserSettings.roles=Roles
 | 
				
			||||||
adminUserSettings.role=Role
 | 
					adminUserSettings.role=Role
 | 
				
			||||||
adminUserSettings.actions=Actions
 | 
					adminUserSettings.actions=Actions
 | 
				
			||||||
@ -183,6 +185,7 @@ adminUserSettings.internalApiUser=Internal API User
 | 
				
			|||||||
adminUserSettings.forceChange=Force user to change password on login
 | 
					adminUserSettings.forceChange=Force user to change password on login
 | 
				
			||||||
adminUserSettings.submit=Save User
 | 
					adminUserSettings.submit=Save User
 | 
				
			||||||
adminUserSettings.changeUserRole=Alterar Função de Usuário
 | 
					adminUserSettings.changeUserRole=Alterar Função de Usuário
 | 
				
			||||||
 | 
					adminUserSettings.authenticated=Authenticated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#############
 | 
					#############
 | 
				
			||||||
# HOME-PAGE #
 | 
					# HOME-PAGE #
 | 
				
			||||||
@ -802,7 +805,7 @@ pdfOrganiser.placeholder=(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)
 | 
				
			|||||||
#multiTool
 | 
					#multiTool
 | 
				
			||||||
multiTool.title=Multiferramenta de PDF
 | 
					multiTool.title=Multiferramenta de PDF
 | 
				
			||||||
multiTool.header=Multiferramenta de PDF
 | 
					multiTool.header=Multiferramenta de PDF
 | 
				
			||||||
multiTool.uploadPrompts=Please Upload PDF
 | 
					multiTool.uploadPrompts=File Name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#view pdf
 | 
					#view pdf
 | 
				
			||||||
viewPdf.title=View PDF
 | 
					viewPdf.title=View PDF
 | 
				
			||||||
@ -1057,7 +1060,7 @@ licenses.version=Version
 | 
				
			|||||||
licenses.license=License
 | 
					licenses.license=License
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# error
 | 
					#error
 | 
				
			||||||
error.sorry=Sorry for the issue!
 | 
					error.sorry=Sorry for the issue!
 | 
				
			||||||
error.needHelp=Need help / Found an issue?
 | 
					error.needHelp=Need help / Found an issue?
 | 
				
			||||||
error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
 | 
					error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
 | 
				
			||||||
 | 
				
			|||||||
@ -54,11 +54,13 @@ notAuthenticatedMessage=Utilizador não autenticado.
 | 
				
			|||||||
userNotFoundMessage=Utilizador inexistente.
 | 
					userNotFoundMessage=Utilizador inexistente.
 | 
				
			||||||
incorrectPasswordMessage=Senha incorreta.
 | 
					incorrectPasswordMessage=Senha incorreta.
 | 
				
			||||||
usernameExistsMessage=Esse utilizador já existe.
 | 
					usernameExistsMessage=Esse utilizador já existe.
 | 
				
			||||||
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
 | 
					invalidUsernameMessage=Invalid username, username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
 | 
				
			||||||
deleteCurrentUserMessage=Cannot delete currently logged in user.
 | 
					deleteCurrentUserMessage=Cannot delete currently logged in user.
 | 
				
			||||||
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
 | 
					deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
 | 
				
			||||||
downgradeCurrentUserMessage=Não é possível fazer downgrade da função do utilizador atual
 | 
					downgradeCurrentUserMessage=Não é possível fazer downgrade da função do utilizador atual
 | 
				
			||||||
downgradeCurrentUserLongMessage=Não é possível fazer downgrade da função do utilizador atual. Portanto, o utilizador atual não será mostrado.
 | 
					downgradeCurrentUserLongMessage=Não é possível fazer downgrade da função do utilizador atual. Portanto, o utilizador atual não será mostrado.
 | 
				
			||||||
 | 
					userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
 | 
				
			||||||
 | 
					userAlreadyExistsWebMessage=The user already exists as an web user.
 | 
				
			||||||
error=Error
 | 
					error=Error
 | 
				
			||||||
oops=Oops!
 | 
					oops=Oops!
 | 
				
			||||||
help=Help
 | 
					help=Help
 | 
				
			||||||
@ -171,7 +173,7 @@ adminUserSettings.header=Admin User Control Settings
 | 
				
			|||||||
adminUserSettings.admin=Admin
 | 
					adminUserSettings.admin=Admin
 | 
				
			||||||
adminUserSettings.user=User
 | 
					adminUserSettings.user=User
 | 
				
			||||||
adminUserSettings.addUser=Add New User
 | 
					adminUserSettings.addUser=Add New User
 | 
				
			||||||
adminUserSettings.usernameInfo=Username must only contain letters and numbers, no spaces or special characters.
 | 
					adminUserSettings.usernameInfo=Username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
 | 
				
			||||||
adminUserSettings.roles=Roles
 | 
					adminUserSettings.roles=Roles
 | 
				
			||||||
adminUserSettings.role=Role
 | 
					adminUserSettings.role=Role
 | 
				
			||||||
adminUserSettings.actions=Actions
 | 
					adminUserSettings.actions=Actions
 | 
				
			||||||
@ -183,6 +185,7 @@ adminUserSettings.internalApiUser=Internal API User
 | 
				
			|||||||
adminUserSettings.forceChange=Force user to change password on login
 | 
					adminUserSettings.forceChange=Force user to change password on login
 | 
				
			||||||
adminUserSettings.submit=Save User
 | 
					adminUserSettings.submit=Save User
 | 
				
			||||||
adminUserSettings.changeUserRole=Alterar usuário
 | 
					adminUserSettings.changeUserRole=Alterar usuário
 | 
				
			||||||
 | 
					adminUserSettings.authenticated=Authenticated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#############
 | 
					#############
 | 
				
			||||||
# HOME-PAGE #
 | 
					# HOME-PAGE #
 | 
				
			||||||
@ -802,7 +805,7 @@ pdfOrganiser.placeholder=(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)
 | 
				
			|||||||
#multiTool
 | 
					#multiTool
 | 
				
			||||||
multiTool.title=Multiferramenta de PDF
 | 
					multiTool.title=Multiferramenta de PDF
 | 
				
			||||||
multiTool.header=Multiferramenta de PDF
 | 
					multiTool.header=Multiferramenta de PDF
 | 
				
			||||||
multiTool.uploadPrompts=Please Upload PDF
 | 
					multiTool.uploadPrompts=File Name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#view pdf
 | 
					#view pdf
 | 
				
			||||||
viewPdf.title=View PDF
 | 
					viewPdf.title=View PDF
 | 
				
			||||||
@ -1057,7 +1060,7 @@ licenses.version=Versão
 | 
				
			|||||||
licenses.license=Licença
 | 
					licenses.license=Licença
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# error
 | 
					#error
 | 
				
			||||||
error.sorry=Sorry for the issue!
 | 
					error.sorry=Sorry for the issue!
 | 
				
			||||||
error.needHelp=Need help / Found an issue?
 | 
					error.needHelp=Need help / Found an issue?
 | 
				
			||||||
error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
 | 
					error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
 | 
				
			||||||
 | 
				
			|||||||
@ -54,11 +54,13 @@ notAuthenticatedMessage=User not authenticated.
 | 
				
			|||||||
userNotFoundMessage=User not found.
 | 
					userNotFoundMessage=User not found.
 | 
				
			||||||
incorrectPasswordMessage=Current password is incorrect.
 | 
					incorrectPasswordMessage=Current password is incorrect.
 | 
				
			||||||
usernameExistsMessage=New Username already exists.
 | 
					usernameExistsMessage=New Username already exists.
 | 
				
			||||||
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
 | 
					invalidUsernameMessage=Invalid username, username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
 | 
				
			||||||
deleteCurrentUserMessage=Cannot delete currently logged in user.
 | 
					deleteCurrentUserMessage=Cannot delete currently logged in user.
 | 
				
			||||||
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
 | 
					deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
 | 
				
			||||||
downgradeCurrentUserMessage=Rolul utilizatorului curent nu poate fi retrogradat
 | 
					downgradeCurrentUserMessage=Rolul utilizatorului curent nu poate fi retrogradat
 | 
				
			||||||
downgradeCurrentUserLongMessage=Rolul utilizatorului curent nu poate fi retrogradat. Prin urmare, utilizatorul curent nu va fi afișat.
 | 
					downgradeCurrentUserLongMessage=Rolul utilizatorului curent nu poate fi retrogradat. Prin urmare, utilizatorul curent nu va fi afișat.
 | 
				
			||||||
 | 
					userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
 | 
				
			||||||
 | 
					userAlreadyExistsWebMessage=The user already exists as an web user.
 | 
				
			||||||
error=Error
 | 
					error=Error
 | 
				
			||||||
oops=Oops!
 | 
					oops=Oops!
 | 
				
			||||||
help=Help
 | 
					help=Help
 | 
				
			||||||
@ -171,7 +173,7 @@ adminUserSettings.header=Admin User Control Settings
 | 
				
			|||||||
adminUserSettings.admin=Admin
 | 
					adminUserSettings.admin=Admin
 | 
				
			||||||
adminUserSettings.user=User
 | 
					adminUserSettings.user=User
 | 
				
			||||||
adminUserSettings.addUser=Add New User
 | 
					adminUserSettings.addUser=Add New User
 | 
				
			||||||
adminUserSettings.usernameInfo=Username must only contain letters and numbers, no spaces or special characters.
 | 
					adminUserSettings.usernameInfo=Username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
 | 
				
			||||||
adminUserSettings.roles=Roles
 | 
					adminUserSettings.roles=Roles
 | 
				
			||||||
adminUserSettings.role=Role
 | 
					adminUserSettings.role=Role
 | 
				
			||||||
adminUserSettings.actions=Actions
 | 
					adminUserSettings.actions=Actions
 | 
				
			||||||
@ -183,6 +185,7 @@ adminUserSettings.internalApiUser=Internal API User
 | 
				
			|||||||
adminUserSettings.forceChange=Force user to change password on login
 | 
					adminUserSettings.forceChange=Force user to change password on login
 | 
				
			||||||
adminUserSettings.submit=Save User
 | 
					adminUserSettings.submit=Save User
 | 
				
			||||||
adminUserSettings.changeUserRole=Schimbați rolul utilizatorului
 | 
					adminUserSettings.changeUserRole=Schimbați rolul utilizatorului
 | 
				
			||||||
 | 
					adminUserSettings.authenticated=Authenticated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#############
 | 
					#############
 | 
				
			||||||
# HOME-PAGE #
 | 
					# HOME-PAGE #
 | 
				
			||||||
@ -802,7 +805,7 @@ pdfOrganiser.placeholder=(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)
 | 
				
			|||||||
#multiTool
 | 
					#multiTool
 | 
				
			||||||
multiTool.title=Instrument PDF multiplu
 | 
					multiTool.title=Instrument PDF multiplu
 | 
				
			||||||
multiTool.header=Instrument PDF multiplu
 | 
					multiTool.header=Instrument PDF multiplu
 | 
				
			||||||
multiTool.uploadPrompts=Please Upload PDF
 | 
					multiTool.uploadPrompts=File Name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#view pdf
 | 
					#view pdf
 | 
				
			||||||
viewPdf.title=View PDF
 | 
					viewPdf.title=View PDF
 | 
				
			||||||
@ -1057,7 +1060,7 @@ licenses.version=Version
 | 
				
			|||||||
licenses.license=License
 | 
					licenses.license=License
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# error
 | 
					#error
 | 
				
			||||||
error.sorry=Sorry for the issue!
 | 
					error.sorry=Sorry for the issue!
 | 
				
			||||||
error.needHelp=Need help / Found an issue?
 | 
					error.needHelp=Need help / Found an issue?
 | 
				
			||||||
error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
 | 
					error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
 | 
				
			||||||
 | 
				
			|||||||
@ -54,12 +54,13 @@ notAuthenticatedMessage=Пользователь не прошел провер
 | 
				
			|||||||
userNotFoundMessage=Пользователь не найден.
 | 
					userNotFoundMessage=Пользователь не найден.
 | 
				
			||||||
incorrectPasswordMessage=Текущий пароль неверен.
 | 
					incorrectPasswordMessage=Текущий пароль неверен.
 | 
				
			||||||
usernameExistsMessage=Новое имя пользователя уже существует.
 | 
					usernameExistsMessage=Новое имя пользователя уже существует.
 | 
				
			||||||
invalidUsernameMessage=Недопустимое имя пользователя, Имя пользователя должно содержать только буквы алфавита и цифры.
 | 
					invalidUsernameMessage=Неверное имя пользователя. Имя пользователя может содержать только буквы, цифры и следующие специальные символы @._+- или должно быть действительным адресом электронной почты.
 | 
				
			||||||
deleteCurrentUserMessage=Невозможно удалить пользователя, вошедшего в систему.
 | 
					deleteCurrentUserMessage=Невозможно удалить пользователя, вошедшего в систему.
 | 
				
			||||||
deleteUsernameExistsMessage=Имя пользователя не существует и не может быть удалено.
 | 
					deleteUsernameExistsMessage=Имя пользователя не существует и не может быть удалено.
 | 
				
			||||||
info=Info
 | 
					 | 
				
			||||||
downgradeCurrentUserMessage=Невозможно понизить роль текущего пользователя
 | 
					downgradeCurrentUserMessage=Невозможно понизить роль текущего пользователя
 | 
				
			||||||
downgradeCurrentUserLongMessage=Невозможно понизить роль текущего пользователя. Следовательно, текущий пользователь не будет отображаться.
 | 
					downgradeCurrentUserLongMessage=Невозможно понизить роль текущего пользователя. Следовательно, текущий пользователь не будет отображаться.
 | 
				
			||||||
 | 
					userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
 | 
				
			||||||
 | 
					userAlreadyExistsWebMessage=The user already exists as an web user.
 | 
				
			||||||
error=Ошибка
 | 
					error=Ошибка
 | 
				
			||||||
oops=Ой!
 | 
					oops=Ой!
 | 
				
			||||||
help=Помощь
 | 
					help=Помощь
 | 
				
			||||||
@ -70,7 +71,7 @@ visitGithub=Посетить репозиторий на GitHub
 | 
				
			|||||||
donate=Пожертвовать
 | 
					donate=Пожертвовать
 | 
				
			||||||
color=Цвет
 | 
					color=Цвет
 | 
				
			||||||
sponsor=Спонсор
 | 
					sponsor=Спонсор
 | 
				
			||||||
 | 
					info=Info
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -172,7 +173,7 @@ adminUserSettings.header=Настройки контроля пользоват
 | 
				
			|||||||
adminUserSettings.admin=Администратор
 | 
					adminUserSettings.admin=Администратор
 | 
				
			||||||
adminUserSettings.user=Пользователь
 | 
					adminUserSettings.user=Пользователь
 | 
				
			||||||
adminUserSettings.addUser=Добавить нового пользователя
 | 
					adminUserSettings.addUser=Добавить нового пользователя
 | 
				
			||||||
adminUserSettings.usernameInfo=Имя пользователя должно содержать только буквы и цифры, без пробелов и специальных символов.
 | 
					adminUserSettings.usernameInfo=Имя пользователя может содержать только буквы, цифры и следующие специальные символы @._+- или должно быть действительным адресом электронной почты.
 | 
				
			||||||
adminUserSettings.roles=Роли
 | 
					adminUserSettings.roles=Роли
 | 
				
			||||||
adminUserSettings.role=Роль
 | 
					adminUserSettings.role=Роль
 | 
				
			||||||
adminUserSettings.actions=Действия
 | 
					adminUserSettings.actions=Действия
 | 
				
			||||||
@ -184,6 +185,7 @@ adminUserSettings.internalApiUser=Внутренний пользователь
 | 
				
			|||||||
adminUserSettings.forceChange=Просить пользователя изменить пароль при входе в систему
 | 
					adminUserSettings.forceChange=Просить пользователя изменить пароль при входе в систему
 | 
				
			||||||
adminUserSettings.submit=Сохранить пользователя
 | 
					adminUserSettings.submit=Сохранить пользователя
 | 
				
			||||||
adminUserSettings.changeUserRole=Изменить роль пользователя
 | 
					adminUserSettings.changeUserRole=Изменить роль пользователя
 | 
				
			||||||
 | 
					adminUserSettings.authenticated=Authenticated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#############
 | 
					#############
 | 
				
			||||||
# HOME-PAGE #
 | 
					# HOME-PAGE #
 | 
				
			||||||
@ -803,7 +805,7 @@ pdfOrganiser.placeholder=(например, 1,3,2 или 4-8,2,10-12 или 2n-1
 | 
				
			|||||||
#multiTool
 | 
					#multiTool
 | 
				
			||||||
multiTool.title=Мультиинструмент PDF
 | 
					multiTool.title=Мультиинструмент PDF
 | 
				
			||||||
multiTool.header=Мультиинструмент PDF
 | 
					multiTool.header=Мультиинструмент PDF
 | 
				
			||||||
multiTool.uploadPrompts=Пожалуйста, загрузите PDF
 | 
					multiTool.uploadPrompts=File Name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#view pdf
 | 
					#view pdf
 | 
				
			||||||
viewPdf.title=Просмотреть PDF
 | 
					viewPdf.title=Просмотреть PDF
 | 
				
			||||||
@ -1058,7 +1060,7 @@ licenses.version=Версия
 | 
				
			|||||||
licenses.license=Лицензия
 | 
					licenses.license=Лицензия
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# error
 | 
					#error
 | 
				
			||||||
error.sorry=Извините за проблему!
 | 
					error.sorry=Извините за проблему!
 | 
				
			||||||
error.needHelp=Нужна помощь / Нашли проблему?
 | 
					error.needHelp=Нужна помощь / Нашли проблему?
 | 
				
			||||||
error.contactTip=Если у вас все еще есть проблемы, не стесняйтесь обращаться к нам за помощью. Вы можете отправить заявку на нашей странице GitHub или связаться с нами через Discord:
 | 
					error.contactTip=Если у вас все еще есть проблемы, не стесняйтесь обращаться к нам за помощью. Вы можете отправить заявку на нашей странице GitHub или связаться с нами через Discord:
 | 
				
			||||||
 | 
				
			|||||||
@ -59,6 +59,8 @@ deleteCurrentUserMessage=Nie je možné zmazať aktuálne prihláseného použí
 | 
				
			|||||||
deleteUsernameExistsMessage=Používateľské meno neexistuje a nemôže byť zmazané.
 | 
					deleteUsernameExistsMessage=Používateľské meno neexistuje a nemôže byť zmazané.
 | 
				
			||||||
downgradeCurrentUserMessage=Nie je možné znížiť rolu aktuálneho používateľa
 | 
					downgradeCurrentUserMessage=Nie je možné znížiť rolu aktuálneho používateľa
 | 
				
			||||||
downgradeCurrentUserLongMessage=Nie je možné znížiť rolu aktuálneho používateľa. Preto, aktuálny používateľ nebude zobrazený.
 | 
					downgradeCurrentUserLongMessage=Nie je možné znížiť rolu aktuálneho používateľa. Preto, aktuálny používateľ nebude zobrazený.
 | 
				
			||||||
 | 
					userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
 | 
				
			||||||
 | 
					userAlreadyExistsWebMessage=The user already exists as an web user.
 | 
				
			||||||
error=Chyba
 | 
					error=Chyba
 | 
				
			||||||
oops=Ups!
 | 
					oops=Ups!
 | 
				
			||||||
help=Pomoc
 | 
					help=Pomoc
 | 
				
			||||||
@ -69,6 +71,7 @@ visitGithub=Navštíviť GitHub repozitár
 | 
				
			|||||||
donate=Darovať
 | 
					donate=Darovať
 | 
				
			||||||
color=Farba
 | 
					color=Farba
 | 
				
			||||||
sponsor=Sponzorovať
 | 
					sponsor=Sponzorovať
 | 
				
			||||||
 | 
					info=Info
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -102,12 +105,18 @@ pipelineOptions.validateButton=Overiť
 | 
				
			|||||||
#############
 | 
					#############
 | 
				
			||||||
#  NAVBAR   #
 | 
					#  NAVBAR   #
 | 
				
			||||||
#############
 | 
					#############
 | 
				
			||||||
navbar.convert=Konvertovať
 | 
					navbar.favorite=Favorites
 | 
				
			||||||
navbar.security=Bezpečnosť
 | 
					 | 
				
			||||||
navbar.other=Rôzne
 | 
					 | 
				
			||||||
navbar.darkmode=Tmavý režim
 | 
					navbar.darkmode=Tmavý režim
 | 
				
			||||||
navbar.pageOps=Operácie so stránkami
 | 
					navbar.language=Languages
 | 
				
			||||||
navbar.settings=Nastavenia
 | 
					navbar.settings=Nastavenia
 | 
				
			||||||
 | 
					navbar.allTools=Tools
 | 
				
			||||||
 | 
					navbar.multiTool=Multi Tools
 | 
				
			||||||
 | 
					navbar.sections.organize=Organize
 | 
				
			||||||
 | 
					navbar.sections.convertTo=Convert to PDF
 | 
				
			||||||
 | 
					navbar.sections.convertFrom=Convert from PDF
 | 
				
			||||||
 | 
					navbar.sections.security=Sign & Security
 | 
				
			||||||
 | 
					navbar.sections.advance=Advanced
 | 
				
			||||||
 | 
					navbar.sections.edit=View & Edit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#############
 | 
					#############
 | 
				
			||||||
#  SETTINGS #
 | 
					#  SETTINGS #
 | 
				
			||||||
@ -176,6 +185,7 @@ adminUserSettings.internalApiUser=Interný API používateľ
 | 
				
			|||||||
adminUserSettings.forceChange=Donútiť používateľa zmeniť heslo pri prihlásení
 | 
					adminUserSettings.forceChange=Donútiť používateľa zmeniť heslo pri prihlásení
 | 
				
			||||||
adminUserSettings.submit=Uložiť používateľa
 | 
					adminUserSettings.submit=Uložiť používateľa
 | 
				
			||||||
adminUserSettings.changeUserRole=Zmeniť rolu používateľa
 | 
					adminUserSettings.changeUserRole=Zmeniť rolu používateľa
 | 
				
			||||||
 | 
					adminUserSettings.authenticated=Authenticated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#############
 | 
					#############
 | 
				
			||||||
# HOME-PAGE #
 | 
					# HOME-PAGE #
 | 
				
			||||||
@ -742,6 +752,7 @@ extractImages.submit=Extrahovať
 | 
				
			|||||||
fileToPDF.title=Súbor do PDF
 | 
					fileToPDF.title=Súbor do PDF
 | 
				
			||||||
fileToPDF.header=Konvertovať akýkoľvek súbor do PDF
 | 
					fileToPDF.header=Konvertovať akýkoľvek súbor do PDF
 | 
				
			||||||
fileToPDF.credit=Táto služba používa LibreOffice a Unoconv pre konverziu súborov.
 | 
					fileToPDF.credit=Táto služba používa LibreOffice a Unoconv pre konverziu súborov.
 | 
				
			||||||
 | 
					fileToPDF.supportedFileTypesInfo=Supported File types
 | 
				
			||||||
fileToPDF.supportedFileTypes=Podporované typy súborov by mali zahŕňať nižšie uvedené, avšak pre úplný aktualizovaný zoznam podporovaných formátov, prosím, odkazujte na dokumentáciu LibreOffice
 | 
					fileToPDF.supportedFileTypes=Podporované typy súborov by mali zahŕňať nižšie uvedené, avšak pre úplný aktualizovaný zoznam podporovaných formátov, prosím, odkazujte na dokumentáciu LibreOffice
 | 
				
			||||||
fileToPDF.submit=Konvertovať do PDF
 | 
					fileToPDF.submit=Konvertovať do PDF
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -794,7 +805,7 @@ pdfOrganiser.placeholder=(napr. 1,3,2 alebo 4-8,2,10-12 alebo 2n-1)
 | 
				
			|||||||
#multiTool
 | 
					#multiTool
 | 
				
			||||||
multiTool.title=PDF Multi Nástroj
 | 
					multiTool.title=PDF Multi Nástroj
 | 
				
			||||||
multiTool.header=PDF Multi Nástroj
 | 
					multiTool.header=PDF Multi Nástroj
 | 
				
			||||||
multiTool.uploadPrompts=Prosím, nahrajte PDF
 | 
					multiTool.uploadPrompts=File Name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#view pdf
 | 
					#view pdf
 | 
				
			||||||
viewPdf.title=Zobraziť PDF
 | 
					viewPdf.title=Zobraziť PDF
 | 
				
			||||||
@ -1049,7 +1060,7 @@ licenses.version=Verzia
 | 
				
			|||||||
licenses.license=Licencia
 | 
					licenses.license=Licencia
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# error
 | 
					#error
 | 
				
			||||||
error.sorry=Ospravedlňujeme sa za problém!
 | 
					error.sorry=Ospravedlňujeme sa za problém!
 | 
				
			||||||
error.needHelp=Potrebujete pomoc / Našli ste problém?
 | 
					error.needHelp=Potrebujete pomoc / Našli ste problém?
 | 
				
			||||||
error.contactTip=Ak máte stále problémy, neváhajte nás kontaktovať pre pomoc. Môžete podať tiket na našej stránke GitHub alebo nás kontaktovať cez Discord:
 | 
					error.contactTip=Ak máte stále problémy, neváhajte nás kontaktovať pre pomoc. Môžete podať tiket na našej stránke GitHub alebo nás kontaktovať cez Discord:
 | 
				
			||||||
 | 
				
			|||||||
@ -54,11 +54,13 @@ notAuthenticatedMessage=Korisnik nije autentifikovan.
 | 
				
			|||||||
userNotFoundMessage=Korisnik nije pronađen.
 | 
					userNotFoundMessage=Korisnik nije pronađen.
 | 
				
			||||||
incorrectPasswordMessage=Trenutna šifra je netačna.
 | 
					incorrectPasswordMessage=Trenutna šifra je netačna.
 | 
				
			||||||
usernameExistsMessage=Novi korisnik već postoji
 | 
					usernameExistsMessage=Novi korisnik već postoji
 | 
				
			||||||
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
 | 
					invalidUsernameMessage=Invalid username, username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
 | 
				
			||||||
deleteCurrentUserMessage=Cannot delete currently logged in user.
 | 
					deleteCurrentUserMessage=Cannot delete currently logged in user.
 | 
				
			||||||
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
 | 
					deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
 | 
				
			||||||
downgradeCurrentUserMessage=Nije moguće degradirati ulogu trenutnog korisnika
 | 
					downgradeCurrentUserMessage=Nije moguće degradirati ulogu trenutnog korisnika
 | 
				
			||||||
downgradeCurrentUserLongMessage=Nije moguće unazaditi ulogu trenutnog korisnika. Dakle, trenutni korisnik neće biti prikazan.
 | 
					downgradeCurrentUserLongMessage=Nije moguće unazaditi ulogu trenutnog korisnika. Dakle, trenutni korisnik neće biti prikazan.
 | 
				
			||||||
 | 
					userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
 | 
				
			||||||
 | 
					userAlreadyExistsWebMessage=The user already exists as an web user.
 | 
				
			||||||
error=Error
 | 
					error=Error
 | 
				
			||||||
oops=Oops!
 | 
					oops=Oops!
 | 
				
			||||||
help=Help
 | 
					help=Help
 | 
				
			||||||
@ -171,7 +173,7 @@ adminUserSettings.header=Podešavanja kontrole korisnika za administratora
 | 
				
			|||||||
adminUserSettings.admin=Administrator
 | 
					adminUserSettings.admin=Administrator
 | 
				
			||||||
adminUserSettings.user=Korisnik
 | 
					adminUserSettings.user=Korisnik
 | 
				
			||||||
adminUserSettings.addUser=Dodaj novog korisnika
 | 
					adminUserSettings.addUser=Dodaj novog korisnika
 | 
				
			||||||
adminUserSettings.usernameInfo=Username must only contain letters and numbers, no spaces or special characters.
 | 
					adminUserSettings.usernameInfo=Username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
 | 
				
			||||||
adminUserSettings.roles=Uloge
 | 
					adminUserSettings.roles=Uloge
 | 
				
			||||||
adminUserSettings.role=Uloga
 | 
					adminUserSettings.role=Uloga
 | 
				
			||||||
adminUserSettings.actions=Akcije
 | 
					adminUserSettings.actions=Akcije
 | 
				
			||||||
@ -183,6 +185,7 @@ adminUserSettings.internalApiUser=Internal API User
 | 
				
			|||||||
adminUserSettings.forceChange=Prisili korisnika da promeni korisničko ime/lozinku pri prijavi
 | 
					adminUserSettings.forceChange=Prisili korisnika da promeni korisničko ime/lozinku pri prijavi
 | 
				
			||||||
adminUserSettings.submit=Sačuvaj korisnika
 | 
					adminUserSettings.submit=Sačuvaj korisnika
 | 
				
			||||||
adminUserSettings.changeUserRole=Promenite ulogu korisnika
 | 
					adminUserSettings.changeUserRole=Promenite ulogu korisnika
 | 
				
			||||||
 | 
					adminUserSettings.authenticated=Authenticated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#############
 | 
					#############
 | 
				
			||||||
# HOME-PAGE #
 | 
					# HOME-PAGE #
 | 
				
			||||||
@ -802,7 +805,7 @@ pdfOrganiser.placeholder=(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)
 | 
				
			|||||||
#multiTool
 | 
					#multiTool
 | 
				
			||||||
multiTool.title=PDF Multi Alatka
 | 
					multiTool.title=PDF Multi Alatka
 | 
				
			||||||
multiTool.header=PDF Multi Alatka
 | 
					multiTool.header=PDF Multi Alatka
 | 
				
			||||||
multiTool.uploadPrompts=Please Upload PDF
 | 
					multiTool.uploadPrompts=File Name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#view pdf
 | 
					#view pdf
 | 
				
			||||||
viewPdf.title=Prikaz
 | 
					viewPdf.title=Prikaz
 | 
				
			||||||
@ -1057,7 +1060,7 @@ licenses.version=Version
 | 
				
			|||||||
licenses.license=License
 | 
					licenses.license=License
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# error
 | 
					#error
 | 
				
			||||||
error.sorry=Sorry for the issue!
 | 
					error.sorry=Sorry for the issue!
 | 
				
			||||||
error.needHelp=Need help / Found an issue?
 | 
					error.needHelp=Need help / Found an issue?
 | 
				
			||||||
error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
 | 
					error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
 | 
				
			||||||
 | 
				
			|||||||
@ -54,11 +54,13 @@ notAuthenticatedMessage=User not authenticated.
 | 
				
			|||||||
userNotFoundMessage=User not found.
 | 
					userNotFoundMessage=User not found.
 | 
				
			||||||
incorrectPasswordMessage=Current password is incorrect.
 | 
					incorrectPasswordMessage=Current password is incorrect.
 | 
				
			||||||
usernameExistsMessage=New Username already exists.
 | 
					usernameExistsMessage=New Username already exists.
 | 
				
			||||||
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
 | 
					invalidUsernameMessage=Invalid username, username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
 | 
				
			||||||
deleteCurrentUserMessage=Cannot delete currently logged in user.
 | 
					deleteCurrentUserMessage=Cannot delete currently logged in user.
 | 
				
			||||||
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
 | 
					deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
 | 
				
			||||||
downgradeCurrentUserMessage=Kan inte nedgradera nuvarande användares roll
 | 
					downgradeCurrentUserMessage=Kan inte nedgradera nuvarande användares roll
 | 
				
			||||||
downgradeCurrentUserLongMessage=Kan inte nedgradera nuvarande användares roll. Därför kommer den aktuella användaren inte att visas.
 | 
					downgradeCurrentUserLongMessage=Kan inte nedgradera nuvarande användares roll. Därför kommer den aktuella användaren inte att visas.
 | 
				
			||||||
 | 
					userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
 | 
				
			||||||
 | 
					userAlreadyExistsWebMessage=The user already exists as an web user.
 | 
				
			||||||
error=Error
 | 
					error=Error
 | 
				
			||||||
oops=Oops!
 | 
					oops=Oops!
 | 
				
			||||||
help=Help
 | 
					help=Help
 | 
				
			||||||
@ -171,7 +173,7 @@ adminUserSettings.header=Admin User Control Settings
 | 
				
			|||||||
adminUserSettings.admin=Admin
 | 
					adminUserSettings.admin=Admin
 | 
				
			||||||
adminUserSettings.user=User
 | 
					adminUserSettings.user=User
 | 
				
			||||||
adminUserSettings.addUser=Add New User
 | 
					adminUserSettings.addUser=Add New User
 | 
				
			||||||
adminUserSettings.usernameInfo=Username must only contain letters and numbers, no spaces or special characters.
 | 
					adminUserSettings.usernameInfo=Username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
 | 
				
			||||||
adminUserSettings.roles=Roles
 | 
					adminUserSettings.roles=Roles
 | 
				
			||||||
adminUserSettings.role=Role
 | 
					adminUserSettings.role=Role
 | 
				
			||||||
adminUserSettings.actions=Actions
 | 
					adminUserSettings.actions=Actions
 | 
				
			||||||
@ -183,6 +185,7 @@ adminUserSettings.internalApiUser=Internal API User
 | 
				
			|||||||
adminUserSettings.forceChange=Force user to change password on login
 | 
					adminUserSettings.forceChange=Force user to change password on login
 | 
				
			||||||
adminUserSettings.submit=Save User
 | 
					adminUserSettings.submit=Save User
 | 
				
			||||||
adminUserSettings.changeUserRole=Ändra användarens roll
 | 
					adminUserSettings.changeUserRole=Ändra användarens roll
 | 
				
			||||||
 | 
					adminUserSettings.authenticated=Authenticated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#############
 | 
					#############
 | 
				
			||||||
# HOME-PAGE #
 | 
					# HOME-PAGE #
 | 
				
			||||||
@ -802,7 +805,7 @@ pdfOrganiser.placeholder=(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)
 | 
				
			|||||||
#multiTool
 | 
					#multiTool
 | 
				
			||||||
multiTool.title=PDF-multiverktyg
 | 
					multiTool.title=PDF-multiverktyg
 | 
				
			||||||
multiTool.header=PDF Multi-verktyg
 | 
					multiTool.header=PDF Multi-verktyg
 | 
				
			||||||
multiTool.uploadPrompts=Please Upload PDF
 | 
					multiTool.uploadPrompts=File Name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#view pdf
 | 
					#view pdf
 | 
				
			||||||
viewPdf.title=View PDF
 | 
					viewPdf.title=View PDF
 | 
				
			||||||
@ -1057,7 +1060,7 @@ licenses.version=Version
 | 
				
			|||||||
licenses.license=License
 | 
					licenses.license=License
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# error
 | 
					#error
 | 
				
			||||||
error.sorry=Sorry for the issue!
 | 
					error.sorry=Sorry for the issue!
 | 
				
			||||||
error.needHelp=Need help / Found an issue?
 | 
					error.needHelp=Need help / Found an issue?
 | 
				
			||||||
error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
 | 
					error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
 | 
				
			||||||
 | 
				
			|||||||
@ -54,11 +54,13 @@ notAuthenticatedMessage=Kullanıcı doğrulanmadı.
 | 
				
			|||||||
userNotFoundMessage=Kullanıcı bulunamadı.
 | 
					userNotFoundMessage=Kullanıcı bulunamadı.
 | 
				
			||||||
incorrectPasswordMessage=Mevcut şifre yanlış.
 | 
					incorrectPasswordMessage=Mevcut şifre yanlış.
 | 
				
			||||||
usernameExistsMessage=Yeni Kullanıcı Adı zaten var.
 | 
					usernameExistsMessage=Yeni Kullanıcı Adı zaten var.
 | 
				
			||||||
invalidUsernameMessage=Geçersiz kullanıcı adı, Kullanıcı adı yalnızca alfabe karakterleri ve sayılar içermelidir.
 | 
					invalidUsernameMessage=Geçersiz kullanıcı adı, kullanıcı adı yalnızca harf, rakam ve aşağıdaki özel karakterleri @._+- içerebilir veya geçerli bir e-posta adresi olmalıdır.
 | 
				
			||||||
deleteCurrentUserMessage=Şu anda oturum açmış olan kullanıcı silinemiyor.
 | 
					deleteCurrentUserMessage=Şu anda oturum açmış olan kullanıcı silinemiyor.
 | 
				
			||||||
deleteUsernameExistsMessage=Kullanıcı adı mevcut değil ve silinemez.
 | 
					deleteUsernameExistsMessage=Kullanıcı adı mevcut değil ve silinemez.
 | 
				
			||||||
downgradeCurrentUserMessage=Mevcut kullanıcının rolü düşürülemiyor
 | 
					downgradeCurrentUserMessage=Mevcut kullanıcının rolü düşürülemiyor
 | 
				
			||||||
downgradeCurrentUserLongMessage=Mevcut kullanıcının rolü düşürülemiyor. Bu nedenle, mevcut kullanıcı gösterilmeyecektir.
 | 
					downgradeCurrentUserLongMessage=Mevcut kullanıcının rolü düşürülemiyor. Bu nedenle, mevcut kullanıcı gösterilmeyecektir.
 | 
				
			||||||
 | 
					userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
 | 
				
			||||||
 | 
					userAlreadyExistsWebMessage=The user already exists as an web user.
 | 
				
			||||||
error=Hata
 | 
					error=Hata
 | 
				
			||||||
oops=Tüh!
 | 
					oops=Tüh!
 | 
				
			||||||
help=Yardım
 | 
					help=Yardım
 | 
				
			||||||
@ -68,8 +70,8 @@ seeDockerHub=Docker Hub'a bakın
 | 
				
			|||||||
visitGithub=Github Deposunu Ziyaret Edin
 | 
					visitGithub=Github Deposunu Ziyaret Edin
 | 
				
			||||||
donate=Bağış Yapın
 | 
					donate=Bağış Yapın
 | 
				
			||||||
color=Renk
 | 
					color=Renk
 | 
				
			||||||
info=Info
 | 
					 | 
				
			||||||
sponsor=Bağış
 | 
					sponsor=Bağış
 | 
				
			||||||
 | 
					info=Info
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -171,7 +173,7 @@ adminUserSettings.header=Yönetici Kullanıcı Kontrol Ayarları
 | 
				
			|||||||
adminUserSettings.admin=Yönetici
 | 
					adminUserSettings.admin=Yönetici
 | 
				
			||||||
adminUserSettings.user=Kullanıcı
 | 
					adminUserSettings.user=Kullanıcı
 | 
				
			||||||
adminUserSettings.addUser=Yeni Kullanıcı Ekle
 | 
					adminUserSettings.addUser=Yeni Kullanıcı Ekle
 | 
				
			||||||
adminUserSettings.usernameInfo=Kullanıcı adı yalnızca harf ve rakamlardan oluşmalı, boşluk veya özel karakter içermemelidir.
 | 
					adminUserSettings.usernameInfo=Kullanıcı adı yalnızca harf, rakam ve aşağıdaki özel karakterleri @._+- içerebilir veya geçerli bir e-posta adresi olmalıdır.
 | 
				
			||||||
adminUserSettings.roles=Roller
 | 
					adminUserSettings.roles=Roller
 | 
				
			||||||
adminUserSettings.role=Rol
 | 
					adminUserSettings.role=Rol
 | 
				
			||||||
adminUserSettings.actions=Eylemler
 | 
					adminUserSettings.actions=Eylemler
 | 
				
			||||||
@ -183,6 +185,7 @@ adminUserSettings.internalApiUser=Dahili API Kullanıcısı
 | 
				
			|||||||
adminUserSettings.forceChange=Kullanıcının girişte kullanıcı adı/şifre değiştirmesini zorla
 | 
					adminUserSettings.forceChange=Kullanıcının girişte kullanıcı adı/şifre değiştirmesini zorla
 | 
				
			||||||
adminUserSettings.submit=Kullanıcıyı Kaydet
 | 
					adminUserSettings.submit=Kullanıcıyı Kaydet
 | 
				
			||||||
adminUserSettings.changeUserRole=Kullanıcı rolünü değiştir
 | 
					adminUserSettings.changeUserRole=Kullanıcı rolünü değiştir
 | 
				
			||||||
 | 
					adminUserSettings.authenticated=Authenticated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#############
 | 
					#############
 | 
				
			||||||
# HOME-PAGE #
 | 
					# HOME-PAGE #
 | 
				
			||||||
@ -802,7 +805,7 @@ pdfOrganiser.placeholder=(örn. 1,3,2 veya 4-8,2,10-12 veya 2n-1)
 | 
				
			|||||||
#multiTool
 | 
					#multiTool
 | 
				
			||||||
multiTool.title=PDF Çoklu Araç
 | 
					multiTool.title=PDF Çoklu Araç
 | 
				
			||||||
multiTool.header=PDF Çoklu Araç
 | 
					multiTool.header=PDF Çoklu Araç
 | 
				
			||||||
multiTool.uploadPrompts=Lütfen PDF Yükleyin
 | 
					multiTool.uploadPrompts=File Name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#view pdf
 | 
					#view pdf
 | 
				
			||||||
viewPdf.title=PDF Görüntüle
 | 
					viewPdf.title=PDF Görüntüle
 | 
				
			||||||
@ -1057,7 +1060,7 @@ licenses.version=Versiyon
 | 
				
			|||||||
licenses.license=Lisans
 | 
					licenses.license=Lisans
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# error
 | 
					#error
 | 
				
			||||||
error.sorry=Sorun için özür dileriz!
 | 
					error.sorry=Sorun için özür dileriz!
 | 
				
			||||||
error.needHelp=Yardıma mı ihtiyacınız var / Bir sorun mu buldunuz?
 | 
					error.needHelp=Yardıma mı ihtiyacınız var / Bir sorun mu buldunuz?
 | 
				
			||||||
error.contactTip=Hala sorun yaşıyorsanız, yardım için bize ulaşmaktan çekinmeyin. GitHub sayfamızdan bir bilet gönderebilir veya Discord üzerinden bizimle iletişime geçebilirsiniz:
 | 
					error.contactTip=Hala sorun yaşıyorsanız, yardım için bize ulaşmaktan çekinmeyin. GitHub sayfamızdan bir bilet gönderebilir veya Discord üzerinden bizimle iletişime geçebilirsiniz:
 | 
				
			||||||
 | 
				
			|||||||
@ -54,11 +54,13 @@ notAuthenticatedMessage=Користувач не пройшов перевір
 | 
				
			|||||||
userNotFoundMessage=Користувача не знайдено.
 | 
					userNotFoundMessage=Користувача не знайдено.
 | 
				
			||||||
incorrectPasswordMessage=Поточний пароль невірний.
 | 
					incorrectPasswordMessage=Поточний пароль невірний.
 | 
				
			||||||
usernameExistsMessage=Нове ім'я користувача вже існує.
 | 
					usernameExistsMessage=Нове ім'я користувача вже існує.
 | 
				
			||||||
invalidUsernameMessage=Недійсне ім'я користувача, Ім'я користувача повинно містити тільки літери алфавіту та цифри.
 | 
					invalidUsernameMessage=Недійсне ім’я користувача, ім’я користувача може містити лише літери, цифри та наступні спеціальні символи @._+- або має бути дійсною електронною адресою.
 | 
				
			||||||
deleteCurrentUserMessage=Неможливо видалити користувача, який увійшов в систему.
 | 
					deleteCurrentUserMessage=Неможливо видалити користувача, який увійшов в систему.
 | 
				
			||||||
deleteUsernameExistsMessage=Ім'я користувача не існує і не може бути видалено.
 | 
					deleteUsernameExistsMessage=Ім'я користувача не існує і не може бути видалено.
 | 
				
			||||||
downgradeCurrentUserMessage=Неможливо понизити роль поточного користувача
 | 
					downgradeCurrentUserMessage=Неможливо понизити роль поточного користувача
 | 
				
			||||||
downgradeCurrentUserLongMessage=Неможливо понизити роль поточного користувача. Отже, поточний користувач не відображатиметься.
 | 
					downgradeCurrentUserLongMessage=Неможливо понизити роль поточного користувача. Отже, поточний користувач не відображатиметься.
 | 
				
			||||||
 | 
					userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
 | 
				
			||||||
 | 
					userAlreadyExistsWebMessage=The user already exists as an web user.
 | 
				
			||||||
error=Error
 | 
					error=Error
 | 
				
			||||||
oops=Oops!
 | 
					oops=Oops!
 | 
				
			||||||
help=Help
 | 
					help=Help
 | 
				
			||||||
@ -171,7 +173,7 @@ adminUserSettings.header=Налаштування контролю корист
 | 
				
			|||||||
adminUserSettings.admin=Адміністратор
 | 
					adminUserSettings.admin=Адміністратор
 | 
				
			||||||
adminUserSettings.user=Користувач
 | 
					adminUserSettings.user=Користувач
 | 
				
			||||||
adminUserSettings.addUser=Додати нового користувача
 | 
					adminUserSettings.addUser=Додати нового користувача
 | 
				
			||||||
adminUserSettings.usernameInfo=Ім'я користувача має містити тільки літери та цифри, без пробілів та спеціальних символів.
 | 
					adminUserSettings.usernameInfo=Ім’я користувача може містити лише літери, цифри та наступні спеціальні символи @._+- або має бути дійсною електронною адресою.
 | 
				
			||||||
adminUserSettings.roles=Ролі
 | 
					adminUserSettings.roles=Ролі
 | 
				
			||||||
adminUserSettings.role=Роль
 | 
					adminUserSettings.role=Роль
 | 
				
			||||||
adminUserSettings.actions=Дії
 | 
					adminUserSettings.actions=Дії
 | 
				
			||||||
@ -183,6 +185,7 @@ adminUserSettings.internalApiUser=Внутрішній користувач API
 | 
				
			|||||||
adminUserSettings.forceChange=Примусити користувача змінити пароль при вході в систему
 | 
					adminUserSettings.forceChange=Примусити користувача змінити пароль при вході в систему
 | 
				
			||||||
adminUserSettings.submit=Зберегти користувача
 | 
					adminUserSettings.submit=Зберегти користувача
 | 
				
			||||||
adminUserSettings.changeUserRole=Змінити роль користувача
 | 
					adminUserSettings.changeUserRole=Змінити роль користувача
 | 
				
			||||||
 | 
					adminUserSettings.authenticated=Authenticated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#############
 | 
					#############
 | 
				
			||||||
# HOME-PAGE #
 | 
					# HOME-PAGE #
 | 
				
			||||||
@ -802,7 +805,7 @@ pdfOrganiser.placeholder=(наприклад, 1,3,2 або 4-8,2,10-12 або 2n
 | 
				
			|||||||
#multiTool
 | 
					#multiTool
 | 
				
			||||||
multiTool.title=Мультіінструмент PDF
 | 
					multiTool.title=Мультіінструмент PDF
 | 
				
			||||||
multiTool.header=Мультіінструмент PDF
 | 
					multiTool.header=Мультіінструмент PDF
 | 
				
			||||||
multiTool.uploadPrompts=Будь ласка, завантажте PDF
 | 
					multiTool.uploadPrompts=File Name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#view pdf
 | 
					#view pdf
 | 
				
			||||||
viewPdf.title=Переглянути PDF
 | 
					viewPdf.title=Переглянути PDF
 | 
				
			||||||
@ -1057,7 +1060,7 @@ licenses.version=Версія
 | 
				
			|||||||
licenses.license=Ліцензія
 | 
					licenses.license=Ліцензія
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# error
 | 
					#error
 | 
				
			||||||
error.sorry=Вибачте за незручності!
 | 
					error.sorry=Вибачте за незручності!
 | 
				
			||||||
error.needHelp=Потрібна допомога / Знайшли проблему?
 | 
					error.needHelp=Потрібна допомога / Знайшли проблему?
 | 
				
			||||||
error.contactTip=Якщо у вас досі виникають проблеми, не соромтеся звертатися до нас за допомогою. Ви можете надіслати запит на нашій сторінці GitHub або зв'язатися з нами через Discord:
 | 
					error.contactTip=Якщо у вас досі виникають проблеми, не соромтеся звертатися до нас за допомогою. Ви можете надіслати запит на нашій сторінці GitHub або зв'язатися з нами через Discord:
 | 
				
			||||||
 | 
				
			|||||||
@ -54,11 +54,13 @@ notAuthenticatedMessage=用户未经过身份验证。
 | 
				
			|||||||
userNotFoundMessage=未找到用户。
 | 
					userNotFoundMessage=未找到用户。
 | 
				
			||||||
incorrectPasswordMessage=当前密码不正确。
 | 
					incorrectPasswordMessage=当前密码不正确。
 | 
				
			||||||
usernameExistsMessage=新用户名已存在。
 | 
					usernameExistsMessage=新用户名已存在。
 | 
				
			||||||
invalidUsernameMessage=用户名无效,用户名只能由字母字符和数字组成。
 | 
					invalidUsernameMessage=用户名无效,用户名只能包含字母、数字和以下特殊字符@._+- 或必须是有效的电子邮件地址。
 | 
				
			||||||
deleteCurrentUserMessage=无法删除当前登录的用户。
 | 
					deleteCurrentUserMessage=无法删除当前登录的用户。
 | 
				
			||||||
deleteUsernameExistsMessage=用户名不存在,无法删除。
 | 
					deleteUsernameExistsMessage=用户名不存在,无法删除。
 | 
				
			||||||
downgradeCurrentUserMessage=无法降级当前用户的角色
 | 
					downgradeCurrentUserMessage=无法降级当前用户的角色
 | 
				
			||||||
downgradeCurrentUserLongMessage=无法降级当前用户的角色。因此,当前用户将不会显示。
 | 
					downgradeCurrentUserLongMessage=无法降级当前用户的角色。因此,当前用户将不会显示。
 | 
				
			||||||
 | 
					userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
 | 
				
			||||||
 | 
					userAlreadyExistsWebMessage=The user already exists as an web user.
 | 
				
			||||||
error=错误
 | 
					error=错误
 | 
				
			||||||
oops=哎呀!
 | 
					oops=哎呀!
 | 
				
			||||||
help=帮助
 | 
					help=帮助
 | 
				
			||||||
@ -171,7 +173,7 @@ adminUserSettings.header=管理员用户控制设置
 | 
				
			|||||||
adminUserSettings.admin=管理员
 | 
					adminUserSettings.admin=管理员
 | 
				
			||||||
adminUserSettings.user=用户
 | 
					adminUserSettings.user=用户
 | 
				
			||||||
adminUserSettings.addUser=添加新用户
 | 
					adminUserSettings.addUser=添加新用户
 | 
				
			||||||
adminUserSettings.usernameInfo=用户名只能由字母和数字组成,不能包含空格或特殊字符。
 | 
					adminUserSettings.usernameInfo=用户名只能包含字母、数字和以下特殊字符@._+-,或者必须是有效的电子邮件地址。
 | 
				
			||||||
adminUserSettings.roles=角色
 | 
					adminUserSettings.roles=角色
 | 
				
			||||||
adminUserSettings.role=角色
 | 
					adminUserSettings.role=角色
 | 
				
			||||||
adminUserSettings.actions=操作
 | 
					adminUserSettings.actions=操作
 | 
				
			||||||
@ -183,6 +185,7 @@ adminUserSettings.internalApiUser=内部API用户
 | 
				
			|||||||
adminUserSettings.forceChange=强制用户在登录时更改用户名/密码
 | 
					adminUserSettings.forceChange=强制用户在登录时更改用户名/密码
 | 
				
			||||||
adminUserSettings.submit=保存用户
 | 
					adminUserSettings.submit=保存用户
 | 
				
			||||||
adminUserSettings.changeUserRole=更改用户角色
 | 
					adminUserSettings.changeUserRole=更改用户角色
 | 
				
			||||||
 | 
					adminUserSettings.authenticated=Authenticated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#############
 | 
					#############
 | 
				
			||||||
# HOME-PAGE #
 | 
					# HOME-PAGE #
 | 
				
			||||||
@ -802,7 +805,7 @@ pdfOrganiser.placeholder=(例如 1,3,2 或 4-8,2,10-12 或 2n-1)
 | 
				
			|||||||
#multiTool
 | 
					#multiTool
 | 
				
			||||||
multiTool.title=PDF多功能工具
 | 
					multiTool.title=PDF多功能工具
 | 
				
			||||||
multiTool.header=PDF多功能工具
 | 
					multiTool.header=PDF多功能工具
 | 
				
			||||||
multiTool.uploadPrompts=上传PDF
 | 
					multiTool.uploadPrompts=File Name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#view pdf
 | 
					#view pdf
 | 
				
			||||||
viewPdf.title=浏览PDF
 | 
					viewPdf.title=浏览PDF
 | 
				
			||||||
@ -1057,7 +1060,7 @@ licenses.version=版本
 | 
				
			|||||||
licenses.license=许可证
 | 
					licenses.license=许可证
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# error
 | 
					#error
 | 
				
			||||||
error.sorry=对此问题感到抱歉!
 | 
					error.sorry=对此问题感到抱歉!
 | 
				
			||||||
error.needHelp=需要帮助 / 发现问题?
 | 
					error.needHelp=需要帮助 / 发现问题?
 | 
				
			||||||
error.contactTip=如果你仍然遇到问题,不要犹豫,向我们寻求帮助。你可以在我们的GitHub页面上提交工单,或者通过Discord与我们联系:
 | 
					error.contactTip=如果你仍然遇到问题,不要犹豫,向我们寻求帮助。你可以在我们的GitHub页面上提交工单,或者通过Discord与我们联系:
 | 
				
			||||||
 | 
				
			|||||||
@ -54,12 +54,13 @@ notAuthenticatedMessage=使用者未認證。
 | 
				
			|||||||
userNotFoundMessage=找不到使用者。
 | 
					userNotFoundMessage=找不到使用者。
 | 
				
			||||||
incorrectPasswordMessage=目前密碼不正確。
 | 
					incorrectPasswordMessage=目前密碼不正確。
 | 
				
			||||||
usernameExistsMessage=新使用者名稱已存在。
 | 
					usernameExistsMessage=新使用者名稱已存在。
 | 
				
			||||||
invalidUsernameMessage=使用者名無效,使用者名只能包含字母字元和數位。
 | 
					invalidUsernameMessage=使用者名稱無效,使用者名稱只能包含字母、數字和以下特殊字元@._+- 或必須是有效的電子郵件地址。
 | 
				
			||||||
deleteCurrentUserMessage=無法刪除目前登錄的使用者。
 | 
					deleteCurrentUserMessage=無法刪除目前登錄的使用者。
 | 
				
			||||||
deleteUsernameExistsMessage=使用者名不存在,無法刪除。
 | 
					deleteUsernameExistsMessage=使用者名不存在,無法刪除。
 | 
				
			||||||
info=Info
 | 
					 | 
				
			||||||
downgradeCurrentUserMessage=無法降級目前使用者的角色
 | 
					downgradeCurrentUserMessage=無法降級目前使用者的角色
 | 
				
			||||||
downgradeCurrentUserLongMessage=無法降級目前使用者的角色。因此,不會顯示目前的使用者。
 | 
					downgradeCurrentUserLongMessage=無法降級目前使用者的角色。因此,不會顯示目前的使用者。
 | 
				
			||||||
 | 
					userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
 | 
				
			||||||
 | 
					userAlreadyExistsWebMessage=The user already exists as an web user.
 | 
				
			||||||
error=錯誤
 | 
					error=錯誤
 | 
				
			||||||
oops=哎呀!
 | 
					oops=哎呀!
 | 
				
			||||||
help=幫助
 | 
					help=幫助
 | 
				
			||||||
@ -70,6 +71,7 @@ visitGithub=訪問Github存儲庫
 | 
				
			|||||||
donate=捐贈
 | 
					donate=捐贈
 | 
				
			||||||
color=顏色
 | 
					color=顏色
 | 
				
			||||||
sponsor=贊助
 | 
					sponsor=贊助
 | 
				
			||||||
 | 
					info=Info
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -171,7 +173,7 @@ adminUserSettings.header=管理使用者控制設定
 | 
				
			|||||||
adminUserSettings.admin=管理員
 | 
					adminUserSettings.admin=管理員
 | 
				
			||||||
adminUserSettings.user=使用者
 | 
					adminUserSettings.user=使用者
 | 
				
			||||||
adminUserSettings.addUser=新增使用者
 | 
					adminUserSettings.addUser=新增使用者
 | 
				
			||||||
adminUserSettings.usernameInfo=使用者名只能包含字母和數位,不能包含空格或特殊字元。
 | 
					adminUserSettings.usernameInfo=使用者名稱只能包含字母、數字和以下特殊字元@._+-,或必須是有效的電子郵件地址。
 | 
				
			||||||
adminUserSettings.roles=角色
 | 
					adminUserSettings.roles=角色
 | 
				
			||||||
adminUserSettings.role=角色
 | 
					adminUserSettings.role=角色
 | 
				
			||||||
adminUserSettings.actions=操作
 | 
					adminUserSettings.actions=操作
 | 
				
			||||||
@ -183,6 +185,7 @@ adminUserSettings.internalApiUser=內部 API 使用者
 | 
				
			|||||||
adminUserSettings.forceChange=強制使用者在登入時修改使用者名稱/密碼
 | 
					adminUserSettings.forceChange=強制使用者在登入時修改使用者名稱/密碼
 | 
				
			||||||
adminUserSettings.submit=儲存
 | 
					adminUserSettings.submit=儲存
 | 
				
			||||||
adminUserSettings.changeUserRole=更改使用者身份
 | 
					adminUserSettings.changeUserRole=更改使用者身份
 | 
				
			||||||
 | 
					adminUserSettings.authenticated=Authenticated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#############
 | 
					#############
 | 
				
			||||||
# HOME-PAGE #
 | 
					# HOME-PAGE #
 | 
				
			||||||
@ -802,7 +805,7 @@ pdfOrganiser.placeholder=(例如 1,3,2 或 4-8,2,10-12 或 2n-1)
 | 
				
			|||||||
#multiTool
 | 
					#multiTool
 | 
				
			||||||
multiTool.title=PDF 多工具
 | 
					multiTool.title=PDF 多工具
 | 
				
			||||||
multiTool.header=PDF 多工具
 | 
					multiTool.header=PDF 多工具
 | 
				
			||||||
multiTool.uploadPrompts=Please Upload PDF
 | 
					multiTool.uploadPrompts=File Name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#view pdf
 | 
					#view pdf
 | 
				
			||||||
viewPdf.title=檢視 PDF
 | 
					viewPdf.title=檢視 PDF
 | 
				
			||||||
@ -1057,7 +1060,7 @@ licenses.version=版本
 | 
				
			|||||||
licenses.license=許可證
 | 
					licenses.license=許可證
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# error
 | 
					#error
 | 
				
			||||||
error.sorry=對於這個問題,我們感到抱歉!
 | 
					error.sorry=對於這個問題,我們感到抱歉!
 | 
				
			||||||
error.needHelp=需要幫助/發現了一個問題?
 | 
					error.needHelp=需要幫助/發現了一個問題?
 | 
				
			||||||
error.contactTip=如果你仍然遇到問題,請不要猶豫,隨時向我們尋求幫助。你可以在我們的GitHub頁面提交工單,或通過Discord與我們聯繋:
 | 
					error.contactTip=如果你仍然遇到問題,請不要猶豫,隨時向我們尋求幫助。你可以在我們的GitHub頁面提交工單,或通過Discord與我們聯繋:
 | 
				
			||||||
 | 
				
			|||||||
@ -7,12 +7,15 @@ security:
 | 
				
			|||||||
  csrfDisabled: true
 | 
					  csrfDisabled: true
 | 
				
			||||||
  loginAttemptCount: 5 # lock user account after 5 tries
 | 
					  loginAttemptCount: 5 # lock user account after 5 tries
 | 
				
			||||||
  loginResetTimeMinutes : 120 # lock account for 2 hours after x attempts
 | 
					  loginResetTimeMinutes : 120 # lock account for 2 hours after x attempts
 | 
				
			||||||
  #oauth2:
 | 
					  # oauth2:
 | 
				
			||||||
  #  enabled: false # set to 'true' to enable login (Note: enableLogin must also be 'true' for this to work)
 | 
					  #   enabled: false # set to 'true' to enable login (Note: enableLogin must also be 'true' for this to work)
 | 
				
			||||||
  #  issuer: "" # set to any provider that supports OpenID Connect Discovery (/.well-known/openid-configuration) end-point
 | 
					  #   issuer: "" # set to any provider that supports OpenID Connect Discovery (/.well-known/openid-configuration) end-point
 | 
				
			||||||
  #  clientId: "" # Client ID from your provider
 | 
					  #   clientId: "" # Client ID from your provider
 | 
				
			||||||
  #  clientSecret: "" # Client Secret from your provider
 | 
					  #   clientSecret: "" # Client Secret from your provider
 | 
				
			||||||
  #  autoCreateUser: false # set to 'true' to allow auto-creation of non-existing users
 | 
					  #   autoCreateUser: false # set to 'true' to allow auto-creation of non-existing users
 | 
				
			||||||
 | 
					  #   useAsUsername: "email" # Default is 'email'; custom fields can be used as the username
 | 
				
			||||||
 | 
					  #   scopes: "openid, profile, email" # Specify the scopes for which the application will request permissions
 | 
				
			||||||
 | 
					  #   provider: "google" # Set this to your OAuth provider's name, e.g., 'google' or 'keycloak'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
system:
 | 
					system:
 | 
				
			||||||
  defaultLocale: 'en-US' # Set the default language (e.g. 'de-DE', 'fr-FR', etc)
 | 
					  defaultLocale: 'en-US' # Set the default language (e.g. 'de-DE', 'fr-FR', etc)
 | 
				
			||||||
@ -20,7 +23,7 @@ system:
 | 
				
			|||||||
  enableAlphaFunctionality: false # Set to enable functionality which might need more testing before it fully goes live (This feature might make no changes)
 | 
					  enableAlphaFunctionality: false # Set to enable functionality which might need more testing before it fully goes live (This feature might make no changes)
 | 
				
			||||||
  showUpdate: true # see when a new update is available
 | 
					  showUpdate: true # see when a new update is available
 | 
				
			||||||
  showUpdateOnlyAdmin: false # Only admins can see when a new update is available, depending on showUpdate it must be set to 'true'
 | 
					  showUpdateOnlyAdmin: false # Only admins can see when a new update is available, depending on showUpdate it must be set to 'true'
 | 
				
			||||||
  
 | 
					
 | 
				
			||||||
ui:
 | 
					ui:
 | 
				
			||||||
  appName: null # Application's visible name
 | 
					  appName: null # Application's visible name
 | 
				
			||||||
  homeDescription: null # Short description or tagline shown on homepage.
 | 
					  homeDescription: null # Short description or tagline shown on homepage.
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										4
									
								
								src/main/resources/static/js/thirdParty/jquery.validate.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/main/resources/static/js/thirdParty/jquery.validate.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@ -1,300 +1,277 @@
 | 
				
			|||||||
<!DOCTYPE html>
 | 
					<!DOCTYPE html>
 | 
				
			||||||
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}"
 | 
					<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="http://www.thymeleaf.org">
 | 
				
			||||||
  xmlns:th="http://www.thymeleaf.org">
 | 
					  <head>
 | 
				
			||||||
 | 
					 | 
				
			||||||
<head>
 | 
					 | 
				
			||||||
  <th:block th:insert="~{fragments/common :: head(title=#{account.title})}"></th:block>
 | 
					  <th:block th:insert="~{fragments/common :: head(title=#{account.title})}"></th:block>
 | 
				
			||||||
</head>
 | 
					  </head>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<body>
 | 
					  <body>
 | 
				
			||||||
  <th:block th:insert="~{fragments/common :: game}"></th:block>
 | 
					    <th:block th:insert="~{fragments/common :: game}"></th:block>
 | 
				
			||||||
  <div id="page-container">
 | 
					    <div id="page-container">
 | 
				
			||||||
    <div id="content-wrap">
 | 
					      <div id="content-wrap">
 | 
				
			||||||
      <th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
 | 
					        <th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
 | 
				
			||||||
      <br><br>
 | 
					        <br><br>
 | 
				
			||||||
      <div class="container">
 | 
					        <div class="container">
 | 
				
			||||||
        <div class="row justify-content-center">
 | 
					          <div class="row justify-content-center">
 | 
				
			||||||
          <div class="col-md-9" id="bg-card">
 | 
					            <div class="col-md-9">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <!-- User Settings Title -->
 | 
					              <!-- User Settings Title -->
 | 
				
			||||||
            <h2 class="text-center" th:text="#{account.accountSettings}">User Settings</h2>
 | 
					              <h2 class="text-center" th:text="#{account.accountSettings}">User Settings</h2>
 | 
				
			||||||
            <th:block th:if="${param.messageType != null and param.messageType.size() > 0}">
 | 
					              <th:block th:if="${messageType}">
 | 
				
			||||||
              <div th:if="${param.messageType[0] == 'notAuthenticated'}" class="alert alert-danger">
 | 
					              <div class="alert alert-danger">
 | 
				
			||||||
                <span th:text="#{notAuthenticatedMessage}">Default message if not found</span>
 | 
					                <span th:text="#{${messageType}}">Default message if not found</span>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
              <div th:if="${param.messageType[0] == 'userNotFound'}" class="alert alert-danger">
 | 
					              </th:block>
 | 
				
			||||||
                <span th:text="#{userNotFoundMessage}">Default message if not found</span>
 | 
					              <!-- At the top of the user settings -->
 | 
				
			||||||
              </div>
 | 
					              <h3 class="text-center"><span th:text="#{welcome} + ' ' + ${username}">User</span>!</h3>
 | 
				
			||||||
              <div th:if="${param.messageType[0] == 'incorrectPassword'}" class="alert alert-danger">
 | 
					              <th:block th:if="${error}">
 | 
				
			||||||
                <span th:text="#{incorrectPasswordMessage}">Default message if not found</span>
 | 
					 | 
				
			||||||
              </div>
 | 
					 | 
				
			||||||
              <div th:if="${param.messageType[0] == 'usernameExists'}" class="alert alert-danger">
 | 
					 | 
				
			||||||
                <span th:text="#{usernameExistsMessage}">Default message if not found</span>
 | 
					 | 
				
			||||||
              </div>
 | 
					 | 
				
			||||||
              <div th:if="${param.messageType[0] == 'invalidUsername'}" class="alert alert-danger">
 | 
					 | 
				
			||||||
                <span th:text="#{invalidUsernameMessage}">Default message if not found</span>
 | 
					 | 
				
			||||||
              </div>
 | 
					 | 
				
			||||||
            </th:block>
 | 
					 | 
				
			||||||
            <!-- At the top of the user settings -->
 | 
					 | 
				
			||||||
            <h3 class="text-center"><span th:text="#{welcome} + ' ' + ${username}">User</span>!</h3>
 | 
					 | 
				
			||||||
            <th:block th:if="${error}">
 | 
					 | 
				
			||||||
              <div class="alert alert-danger" role="alert">
 | 
					              <div class="alert alert-danger" role="alert">
 | 
				
			||||||
                <span th:text="${error}">Error Message</span>
 | 
					                <span th:text="${error}">Error Message</span>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
            </th:block>
 | 
					              </th:block>
 | 
				
			||||||
            <!-- Change Username Form -->
 | 
					              <!-- Change Username Form -->
 | 
				
			||||||
            <h4 th:text="#{account.changeUsername}">Change Username?</h4>
 | 
					              <th:block th:if="${!oAuth2Login}">
 | 
				
			||||||
            <form th:if="${!oAuth2Login}" id="bg-card" class="mt-4 mb-4" action="api/v1/user/change-username" method="post">
 | 
					              <h4 th:text="#{account.changeUsername}">Change Username?</h4>
 | 
				
			||||||
              <div class="mb-3">
 | 
					              <form id="bg-card" class="mt-4 mb-4" action="api/v1/user/change-username" method="post">
 | 
				
			||||||
                <label for="newUsername" th:text="#{account.newUsername}">Change Username</label>
 | 
					                <div class="mb-3">
 | 
				
			||||||
                <input type="text" class="form-control" name="newUsername" id="newUsername"
 | 
					                  <label for="newUsername" th:text="#{account.newUsername}">Change Username</label>
 | 
				
			||||||
                  th:placeholder="#{account.newUsername}">
 | 
					                  <input type="text" class="form-control" name="newUsername" id="newUsername" th:placeholder="#{account.newUsername}">
 | 
				
			||||||
              </div>
 | 
					                </div>
 | 
				
			||||||
              <div class="mb-3">
 | 
					                <div class="mb-3">
 | 
				
			||||||
                <label for="currentPassword" th:text="#{password}">Password</label>
 | 
					                  <label for="currentPassword" th:text="#{password}">Password</label>
 | 
				
			||||||
                <input type="password" class="form-control" name="currentPassword" id="currentPassword"
 | 
					                  <input type="password" class="form-control" name="currentPassword" id="currentPassword" th:placeholder="#{password}">
 | 
				
			||||||
                  th:placeholder="#{password}">
 | 
					                </div>
 | 
				
			||||||
              </div>
 | 
					                <div class="mb-3">
 | 
				
			||||||
              <div class="mb-3">
 | 
					                  <button type="submit" class="btn btn-primary" th:text="#{account.changeUsername}">Change Username</button>
 | 
				
			||||||
                <button type="submit" class="btn btn-primary" th:text="#{account.changeUsername}">Change
 | 
					                </div>
 | 
				
			||||||
                  Username</button>
 | 
					              </form>
 | 
				
			||||||
              </div>
 | 
					              </th:block>
 | 
				
			||||||
            </form>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <!-- Change Password Form -->
 | 
					 | 
				
			||||||
            <h4 th:if="${!oAuth2Login}" th:text="#{account.changePassword}">Change Password?</h4>
 | 
					 | 
				
			||||||
            <form id="bg-card" class="mt-4 mb-4" action="api/v1/user/change-password" method="post">
 | 
					 | 
				
			||||||
              <div class="mb-3">
 | 
					 | 
				
			||||||
                <label for="currentPassword" th:text="#{account.oldPassword}">Old Password</label>
 | 
					 | 
				
			||||||
                <input type="password" class="form-control" name="currentPassword" id="currentPasswordPassword"
 | 
					 | 
				
			||||||
                  th:placeholder="#{account.oldPassword}">
 | 
					 | 
				
			||||||
              </div>
 | 
					 | 
				
			||||||
              <div class="mb-3">
 | 
					 | 
				
			||||||
                <label for="newPassword" th:text="#{account.newPassword}">New Password</label>
 | 
					 | 
				
			||||||
                <input type="password" class="form-control" name="newPassword" id="newPassword"
 | 
					 | 
				
			||||||
                  th:placeholder="#{account.newPassword}">
 | 
					 | 
				
			||||||
              </div>
 | 
					 | 
				
			||||||
              <div class="mb-3">
 | 
					 | 
				
			||||||
                <label for="confirmNewPassword" th:text="#{account.confirmNewPassword}">Confirm New Password</label>
 | 
					 | 
				
			||||||
                <input type="password" class="form-control" name="confirmNewPassword" id="confirmNewPassword"
 | 
					 | 
				
			||||||
                  th:placeholder="#{account.confirmNewPassword}">
 | 
					 | 
				
			||||||
              </div>
 | 
					 | 
				
			||||||
              <div class="mb-3">
 | 
					 | 
				
			||||||
                <button type="submit" class="btn btn-primary" th:text="#{account.changePassword}">Change
 | 
					 | 
				
			||||||
                  Password</button>
 | 
					 | 
				
			||||||
              </div>
 | 
					 | 
				
			||||||
            </form>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              <!-- Change Password Form -->
 | 
				
			||||||
 | 
					              <th:block th:if="${!oAuth2Login}">
 | 
				
			||||||
 | 
					              <h4 th:text="#{account.changePassword}">Change Password?</h4>
 | 
				
			||||||
 | 
					              <form id="bg-card" class="mt-4 mb-4" action="api/v1/user/change-password" method="post">
 | 
				
			||||||
 | 
					                <div class="mb-3">
 | 
				
			||||||
 | 
					                  <label for="currentPassword" th:text="#{account.oldPassword}">Old Password</label>
 | 
				
			||||||
 | 
					                  <input type="password" class="form-control" name="currentPassword" id="currentPasswordPassword" th:placeholder="#{account.oldPassword}">
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <div class="mb-3">
 | 
				
			||||||
 | 
					                  <label for="newPassword" th:text="#{account.newPassword}">New Password</label>
 | 
				
			||||||
 | 
					                  <input type="password" class="form-control" name="newPassword" id="newPassword" th:placeholder="#{account.newPassword}">
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <div class="mb-3">
 | 
				
			||||||
 | 
					                  <label for="confirmNewPassword" th:text="#{account.confirmNewPassword}">Confirm New Password</label>
 | 
				
			||||||
 | 
					                  <input type="password" class="form-control" name="confirmNewPassword" id="confirmNewPassword" th:placeholder="#{account.confirmNewPassword}">
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <div class="mb-3">
 | 
				
			||||||
 | 
					                  <button type="submit" class="btn btn-primary" th:text="#{account.changePassword}">Change Password</button>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					              </form>
 | 
				
			||||||
 | 
					              </th:block>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <!-- API Key Form -->
 | 
					            <!-- API Key Form -->
 | 
				
			||||||
            <h4 th:text="#{account.yourApiKey}">API Key</h4>
 | 
					            <h4 th:text="#{account.yourApiKey}">API Key</h4>
 | 
				
			||||||
            <div class="card mt-4 mb-4">
 | 
					              <div class="card mt-4 mb-4">
 | 
				
			||||||
              <div class="card-header" th:text="#{account.yourApiKey}"></div>
 | 
					                <div class="card-header" th:text="#{account.yourApiKey}"></div>
 | 
				
			||||||
              <div class="card-body">
 | 
					                <div class="card-body">
 | 
				
			||||||
                <div class="input-group mb-3">
 | 
					                  <div class="input-group mb-3">
 | 
				
			||||||
                  <input type="password" class="form-control" id="apiKey" th:placeholder="#{account.yourApiKey}"
 | 
					                    <input type="password" class="form-control" id="apiKey" th:placeholder="#{account.yourApiKey}" readonly>
 | 
				
			||||||
                    readonly>
 | 
					                    <div class="input-group-append">
 | 
				
			||||||
                  <div class="input-group-append">
 | 
					                      <button class="btn btn-secondary" id="copyBtn" type="button" onclick="copyToClipboard()">
 | 
				
			||||||
                    <button class="btn btn-secondary" id="copyBtn" type="button" onclick="copyToClipboard()">
 | 
					                        <span class="material-symbols-rounded">
 | 
				
			||||||
                      <span class="material-symbols-rounded">
 | 
					 | 
				
			||||||
                        content_copy
 | 
					                        content_copy
 | 
				
			||||||
                      </span>
 | 
					                      </span>
 | 
				
			||||||
                    </button>
 | 
					                      </button>
 | 
				
			||||||
                    <button class="btn btn-secondary" id="showBtn" type="button" onclick="showApiKey()">
 | 
					                      <button class="btn btn-secondary" id="showBtn" type="button" onclick="showApiKey()">
 | 
				
			||||||
                      <span class="material-symbols-rounded" id="eyeIcon">
 | 
					                        <span class="material-symbols-rounded" id="eyeIcon">
 | 
				
			||||||
                        visibility
 | 
					                          visibility
 | 
				
			||||||
                      </span>
 | 
					                        </span>
 | 
				
			||||||
                    </button>
 | 
					                      </button>
 | 
				
			||||||
                    <button class="btn btn-secondary" id="refreshBtn" type="button" onclick="refreshApiKey()">
 | 
					                      <button class="btn btn-secondary" id="refreshBtn" type="button" onclick="refreshApiKey()">
 | 
				
			||||||
                      <span class="material-symbols-rounded">
 | 
					                        <span class="material-symbols-rounded">
 | 
				
			||||||
                        refresh
 | 
					                          refresh
 | 
				
			||||||
                      </span>
 | 
					                        </span>
 | 
				
			||||||
                    </button>
 | 
					                      </button>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
                  </div>
 | 
					                  </div>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
            </div>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <script>
 | 
					              <script>
 | 
				
			||||||
              function copyToClipboard() {
 | 
					                function copyToClipboard() {
 | 
				
			||||||
                const apiKeyElement = document.getElementById("apiKey");
 | 
					                  const apiKeyElement = document.getElementById("apiKey");
 | 
				
			||||||
                apiKeyElement.select();
 | 
					                  apiKeyElement.select();
 | 
				
			||||||
                document.execCommand("copy");
 | 
					                  document.execCommand("copy");
 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
              function showApiKey() {
 | 
					 | 
				
			||||||
                const apiKeyElement = document.getElementById("apiKey");
 | 
					 | 
				
			||||||
                const copyBtn = document.getElementById("copyBtn");
 | 
					 | 
				
			||||||
                const eyeIcon = document.getElementById("eyeIcon");
 | 
					 | 
				
			||||||
                if (apiKeyElement.type === "password") {
 | 
					 | 
				
			||||||
                  apiKeyElement.type = "text";
 | 
					 | 
				
			||||||
                  eyeIcon.textContent = "visibility_off";
 | 
					 | 
				
			||||||
                  copyBtn.disabled = false;  // Enable copy button when API key is visible
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                  apiKeyElement.type = "password";
 | 
					 | 
				
			||||||
                  eyeIcon.textContent = "visibility";
 | 
					 | 
				
			||||||
                  copyBtn.disabled = true;  // Disable copy button when API key is hidden
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
              document.addEventListener("DOMContentLoaded", async function () {
 | 
					                function showApiKey() {
 | 
				
			||||||
                try {
 | 
					                  const apiKeyElement = document.getElementById("apiKey");
 | 
				
			||||||
                  let response = await fetch('/api/v1/user/get-api-key', { method: 'POST' });
 | 
					                  const copyBtn = document.getElementById("copyBtn");
 | 
				
			||||||
                  if (response.status === 200) {
 | 
					                  const eyeIcon = document.getElementById("eyeIcon");
 | 
				
			||||||
                    let apiKey = await response.text();
 | 
					                  if (apiKeyElement.type === "password") {
 | 
				
			||||||
                    manageUIState(apiKey);
 | 
					                    apiKeyElement.type = "text";
 | 
				
			||||||
 | 
					                    eyeIcon.textContent = "visibility_off";
 | 
				
			||||||
 | 
					                    copyBtn.disabled = false;  // Enable copy button when API key is visible
 | 
				
			||||||
                  } else {
 | 
					                  } else {
 | 
				
			||||||
                    manageUIState(null);
 | 
					                    apiKeyElement.type = "password";
 | 
				
			||||||
 | 
					                    eyeIcon.textContent = "visibility";
 | 
				
			||||||
 | 
					                    copyBtn.disabled = true;  // Disable copy button when API key is hidden
 | 
				
			||||||
                  }
 | 
					                  }
 | 
				
			||||||
                } catch (error) {
 | 
					 | 
				
			||||||
                  console.error('There was an error:', error);
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
              });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
              async function refreshApiKey() {
 | 
					                document.addEventListener("DOMContentLoaded", async function() {
 | 
				
			||||||
                try {
 | 
					                  try {
 | 
				
			||||||
                  let response = await fetch('/api/v1/user/update-api-key', { method: 'POST' });
 | 
					                    let response = await fetch('/api/v1/user/get-api-key', { method: 'POST' });
 | 
				
			||||||
                  if (response.status === 200) {
 | 
					                    if (response.status === 200) {
 | 
				
			||||||
                    let apiKey = await response.text();
 | 
					                      let apiKey = await response.text();
 | 
				
			||||||
                    manageUIState(apiKey);
 | 
					                      manageUIState(apiKey);
 | 
				
			||||||
                    document.getElementById("apiKey").type = 'text';
 | 
					                    } else {
 | 
				
			||||||
                    document.getElementById("copyBtn").disabled = false;
 | 
					                      manageUIState(null);
 | 
				
			||||||
                  } else {
 | 
					                    }
 | 
				
			||||||
                    alert('Error refreshing API key.');
 | 
					                  } catch (error) {
 | 
				
			||||||
                  }
 | 
					                    console.error('There was an error:', error);
 | 
				
			||||||
                } catch (error) {
 | 
					 | 
				
			||||||
                  console.error('There was an error:', error);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
              function manageUIState(apiKey) {
 | 
					 | 
				
			||||||
                const apiKeyElement = document.getElementById("apiKey");
 | 
					 | 
				
			||||||
                const showBtn = document.getElementById("showBtn");
 | 
					 | 
				
			||||||
                const copyBtn = document.getElementById("copyBtn");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (apiKey && apiKey.trim().length > 0) {
 | 
					 | 
				
			||||||
                  apiKeyElement.value = apiKey;
 | 
					 | 
				
			||||||
                  showBtn.disabled = false;
 | 
					 | 
				
			||||||
                  copyBtn.disabled = true;
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                  apiKeyElement.value = "";
 | 
					 | 
				
			||||||
                  showBtn.disabled = true;
 | 
					 | 
				
			||||||
                  copyBtn.disabled = true;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
              document.addEventListener("DOMContentLoaded", function () {
 | 
					 | 
				
			||||||
                const form = document.querySelector('form[action="api/v1/user/change-password"]');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                form.addEventListener('submit', function (event) {
 | 
					 | 
				
			||||||
                  const newPassword = document.getElementById('newPassword').value;
 | 
					 | 
				
			||||||
                  const confirmNewPassword = document.getElementById('confirmNewPassword').value;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                  if (newPassword !== confirmNewPassword) {
 | 
					 | 
				
			||||||
                    alert('New Password and Confirm New Password must match.');
 | 
					 | 
				
			||||||
                    event.preventDefault(); // Prevent form submission
 | 
					 | 
				
			||||||
                  }
 | 
					                  }
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
              });
 | 
					 | 
				
			||||||
            </script>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <h4 th:text="#{account.syncTitle}">Sync browser settings with Account</h4>
 | 
					                async function refreshApiKey() {
 | 
				
			||||||
            <div id="bg-card" class="container mt-4">
 | 
					                  try {
 | 
				
			||||||
              <h3 th:text="#{account.settingsCompare}">Settings Comparison:</h3>
 | 
					                    let response = await fetch('/api/v1/user/update-api-key', { method: 'POST' });
 | 
				
			||||||
              <table id="settingsTable" class="table table-bordered table-sm table-striped">
 | 
					                    if (response.status === 200) {
 | 
				
			||||||
                <thead>
 | 
					                      let apiKey = await response.text();
 | 
				
			||||||
                  <tr>
 | 
					                      manageUIState(apiKey);
 | 
				
			||||||
                    <th th:text="#{account.property}">Property</th>
 | 
					                      document.getElementById("apiKey").type = 'text';
 | 
				
			||||||
                    <th th:text="#{account.accountSettings}">Account Setting</th>
 | 
					                      document.getElementById("copyBtn").disabled = false;
 | 
				
			||||||
                    <th th:text="#{account.webBrowserSettings}">Web Browser Setting</th>
 | 
					                    } else {
 | 
				
			||||||
                  </tr>
 | 
					                      alert('Error refreshing API key.');
 | 
				
			||||||
                </thead>
 | 
					                    }
 | 
				
			||||||
                <tbody>
 | 
					                  } catch (error) {
 | 
				
			||||||
                  <!-- This will be dynamically populated by JavaScript -->
 | 
					                    console.error('There was an error:', error);
 | 
				
			||||||
                </tbody>
 | 
					                  }
 | 
				
			||||||
              </table>
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              <div class="buttons-container mt-3 text-center">
 | 
					                function manageUIState(apiKey) {
 | 
				
			||||||
                <button id="syncToBrowser" class="btn btn-primary btn-sm" th:text="#{account.syncToBrowser}">Sync
 | 
					                  const apiKeyElement = document.getElementById("apiKey");
 | 
				
			||||||
                  Account -> Browser</button>
 | 
					                  const showBtn = document.getElementById("showBtn");
 | 
				
			||||||
                <button id="syncToAccount" class="btn btn-secondary btn-sm" th:text="#{account.syncToAccount}">Sync
 | 
					                  const copyBtn = document.getElementById("copyBtn");
 | 
				
			||||||
                  Account <- Browser</button>
 | 
					
 | 
				
			||||||
 | 
					                  if (apiKey && apiKey.trim().length > 0) {
 | 
				
			||||||
 | 
					                    apiKeyElement.value = apiKey;
 | 
				
			||||||
 | 
					                    showBtn.disabled = false;
 | 
				
			||||||
 | 
					                    copyBtn.disabled = true;
 | 
				
			||||||
 | 
					                  } else {
 | 
				
			||||||
 | 
					                    apiKeyElement.value = "";
 | 
				
			||||||
 | 
					                    showBtn.disabled = true;
 | 
				
			||||||
 | 
					                    copyBtn.disabled = true;
 | 
				
			||||||
 | 
					                  }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                document.addEventListener("DOMContentLoaded", function() {
 | 
				
			||||||
 | 
					                  const form = document.querySelector('form[action="api/v1/user/change-password"]');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                  form.addEventListener('submit', function(event) {
 | 
				
			||||||
 | 
					                    const newPassword = document.getElementById('newPassword').value;
 | 
				
			||||||
 | 
					                    const confirmNewPassword = document.getElementById('confirmNewPassword').value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (newPassword !== confirmNewPassword) {
 | 
				
			||||||
 | 
					                      alert('New Password and Confirm New Password must match.');
 | 
				
			||||||
 | 
					                      event.preventDefault(); // Prevent form submission
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                  });
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					              </script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              <h4 th:text="#{account.syncTitle}">Sync browser settings with Account</h4>
 | 
				
			||||||
 | 
					              <div id="bg-card" class="container mt-4">
 | 
				
			||||||
 | 
					                <h3 th:text="#{account.settingsCompare}">Settings Comparison:</h3>
 | 
				
			||||||
 | 
					                <table id="settingsTable" class="table table-bordered table-sm table-striped">
 | 
				
			||||||
 | 
					                  <thead>
 | 
				
			||||||
 | 
					                    <tr>
 | 
				
			||||||
 | 
					                      <th th:text="#{account.property}">Property</th>
 | 
				
			||||||
 | 
					                      <th th:text="#{account.accountSettings}">Account Setting</th>
 | 
				
			||||||
 | 
					                      <th th:text="#{account.webBrowserSettings}">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" th:text="#{account.syncToBrowser}">Sync Account -> Browser</button>
 | 
				
			||||||
 | 
					                  <button id="syncToAccount" class="btn btn-secondary btn-sm" th:text="#{account.syncToAccount}">Sync Account <- Browser</button>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
            </div>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <script th:inline="javascript">
 | 
					              <script th:inline="javascript">
 | 
				
			||||||
              document.addEventListener("DOMContentLoaded", function () {
 | 
					                document.addEventListener("DOMContentLoaded", function() {
 | 
				
			||||||
                const settingsTableBody = document.querySelector("#settingsTable tbody");
 | 
					                  const settingsTableBody = document.querySelector("#settingsTable tbody");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                /*<![CDATA[*/
 | 
					                  /*<![CDATA[*/
 | 
				
			||||||
                var accountSettingsString = /*[[${settings}]]*/ {};
 | 
					                  var accountSettingsString = /*[[${settings}]]*/ {};
 | 
				
			||||||
                /*]]>*/
 | 
					                  /*]]>*/
 | 
				
			||||||
                var accountSettings = JSON.parse(accountSettingsString);
 | 
					                  var accountSettings = JSON.parse(accountSettingsString);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                let allKeys = new Set([...Object.keys(accountSettings), ...Object.keys(localStorage)]);
 | 
					                  let allKeys = new Set([...Object.keys(accountSettings), ...Object.keys(localStorage)]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                allKeys.forEach(key => {
 | 
					                  allKeys.forEach(key => {
 | 
				
			||||||
                  if (key === 'debug' || key === '0' || key === '1') return;  // Ignoring specific keys
 | 
					                    if(key === 'debug' || key === '0' || key === '1') return;  // Ignoring specific keys
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                  const accountValue = accountSettings[key] || '-';
 | 
					                    const accountValue = accountSettings[key] || '-';
 | 
				
			||||||
                  const browserValue = localStorage.getItem(key) || '-';
 | 
					                    const browserValue = localStorage.getItem(key) || '-';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                  const row = settingsTableBody.insertRow();
 | 
					                    const row = settingsTableBody.insertRow();
 | 
				
			||||||
                  const propertyCell = row.insertCell(0);
 | 
					                    const propertyCell = row.insertCell(0);
 | 
				
			||||||
                  const accountCell = row.insertCell(1);
 | 
					                    const accountCell = row.insertCell(1);
 | 
				
			||||||
                  const browserCell = row.insertCell(2);
 | 
					                    const browserCell = row.insertCell(2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                  propertyCell.textContent = key;
 | 
					                    propertyCell.textContent = key;
 | 
				
			||||||
                  accountCell.textContent = accountValue;
 | 
					                    accountCell.textContent = accountValue;
 | 
				
			||||||
                  browserCell.textContent = browserValue;
 | 
					                    browserCell.textContent = browserValue;
 | 
				
			||||||
                });
 | 
					                  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                document.getElementById('syncToBrowser').addEventListener('click', function () {
 | 
					                  document.getElementById('syncToBrowser').addEventListener('click', function() {
 | 
				
			||||||
                  // First, clear the local storage
 | 
					                    // First, clear the local storage
 | 
				
			||||||
                  localStorage.clear();
 | 
					                    localStorage.clear();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                  // Then, set the account settings to local storage
 | 
					                    // Then, set the account settings to local storage
 | 
				
			||||||
                  for (let key in accountSettings) {
 | 
					                    for (let key in accountSettings) {
 | 
				
			||||||
                    if (key !== 'debug' && key !== '0' && key !== '1') { // Only sync non-ignored keys
 | 
					                      if(key !== 'debug' && key !== '0' && key !== '1') { // Only sync non-ignored keys
 | 
				
			||||||
                      localStorage.setItem(key, accountSettings[key]);
 | 
					                        localStorage.setItem(key, accountSettings[key]);
 | 
				
			||||||
 | 
					                      }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                  }
 | 
					                    location.reload();  // Refresh the page after sync
 | 
				
			||||||
                  location.reload();  // Refresh the page after sync
 | 
					                  });
 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                document.getElementById('syncToAccount').addEventListener('click', function () {
 | 
					                  document.getElementById('syncToAccount').addEventListener('click', function() {
 | 
				
			||||||
                  let form = document.createElement("form");
 | 
					                    let form = document.createElement("form");
 | 
				
			||||||
                  form.method = "POST";
 | 
					                    form.method = "POST";
 | 
				
			||||||
                  form.action = "api/v1/user/updateUserSettings";  // Your endpoint URL
 | 
					                    form.action = "api/v1/user/updateUserSettings";  // Your endpoint URL
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                  for (let i = 0; i < localStorage.length; i++) {
 | 
					                    for (let i = 0; i < localStorage.length; i++) {
 | 
				
			||||||
                    const key = localStorage.key(i);
 | 
					                      const key = localStorage.key(i);
 | 
				
			||||||
                    if (key !== 'debug' && key !== '0' && key !== '1') { // Only send non-ignored keys
 | 
					                      if(key !== 'debug' && key !== '0' && key !== '1') { // Only send non-ignored keys
 | 
				
			||||||
                      let hiddenField = document.createElement("input");
 | 
					                        let hiddenField = document.createElement("input");
 | 
				
			||||||
                      hiddenField.type = "hidden";
 | 
					                        hiddenField.type = "hidden";
 | 
				
			||||||
                      hiddenField.name = key;
 | 
					                        hiddenField.name = key;
 | 
				
			||||||
                      hiddenField.value = localStorage.getItem(key);
 | 
					                        hiddenField.value = localStorage.getItem(key);
 | 
				
			||||||
                      form.appendChild(hiddenField);
 | 
					                        form.appendChild(hiddenField);
 | 
				
			||||||
 | 
					                      }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                  document.body.appendChild(form);
 | 
					                    document.body.appendChild(form);
 | 
				
			||||||
                  form.submit();
 | 
					                    form.submit();
 | 
				
			||||||
 | 
					                  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
 | 
					              </script>
 | 
				
			||||||
              });
 | 
					              <div class="mb-3 mt-4 text-center">
 | 
				
			||||||
            </script>
 | 
					                <a href="logout" role="button" class="btn btn-danger" th:text="#{account.signOut}">Sign Out</a>
 | 
				
			||||||
            <div class="mb-3 mt-4 text-center">
 | 
					                <a th:if="${role == 'ROLE_ADMIN'}" class="btn btn-info" href="addUsers" role="button" th:text="#{account.adminSettings}" target="_blank">Admin Settings</a>
 | 
				
			||||||
              <a href="logout" role="button" class="btn btn-danger" th:text="#{account.signOut}">Sign Out</a>
 | 
					              </div>
 | 
				
			||||||
              <a th:if="${role == 'ROLE_ADMIN'}" class="btn btn-info" href="addUsers" role="button"
 | 
					 | 
				
			||||||
                th:text="#{account.adminSettings}" target="_blank">Admin Settings</a>
 | 
					 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
 | 
					      <th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    <th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
 | 
					  </body>
 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
</body>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
</html>
 | 
					</html>
 | 
				
			||||||
@ -16,9 +16,8 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
              <!-- User Settings Title -->
 | 
					              <!-- User Settings Title -->
 | 
				
			||||||
              <h2 class="text-center" th:text="#{adminUserSettings.header}">Admin User Control Settings</h2>
 | 
					              <h2 class="text-center" th:text="#{adminUserSettings.header}">Admin User Control Settings</h2>
 | 
				
			||||||
              <div th:if="${param.messageType != null and param.messageType.size() > 0 and (param.messageType[0] == 'deleteCurrentUser' or param.messageType[0] == 'deleteUsernameExists')}" class="alert alert-danger">
 | 
					              <div th:if="${deleteMessage}" class="alert alert-danger">
 | 
				
			||||||
                <span th:if="${param.messageType[0] == 'deleteCurrentUser'}" th:text="#{deleteCurrentUserMessage}">Cannot delete currently logged in user.</span>
 | 
					                <span th:text="#{${deleteMessage}}">Message</span>
 | 
				
			||||||
                <span th:if="${param.messageType[0] == 'deleteUsernameExists'}" th:text="#{deleteUsernameExistsMessage}">The username does not exist and cannot be deleted.</span>
 | 
					 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
              <table class="table">
 | 
					              <table class="table">
 | 
				
			||||||
                <thead>
 | 
					                <thead>
 | 
				
			||||||
@ -26,6 +25,7 @@
 | 
				
			|||||||
                    <th th:text="#{username}">Username</th>
 | 
					                    <th th:text="#{username}">Username</th>
 | 
				
			||||||
                    <th th:text="#{adminUserSettings.roles}">Roles</th>
 | 
					                    <th th:text="#{adminUserSettings.roles}">Roles</th>
 | 
				
			||||||
                    <th th:text="#{adminUserSettings.actions}">Actions</th>
 | 
					                    <th th:text="#{adminUserSettings.actions}">Actions</th>
 | 
				
			||||||
 | 
					                    <th th:text="#{adminUserSettings.authenticated}">Authenticated</th>
 | 
				
			||||||
                  </tr>
 | 
					                  </tr>
 | 
				
			||||||
                </thead>
 | 
					                </thead>
 | 
				
			||||||
                <tbody>
 | 
					                <tbody>
 | 
				
			||||||
@ -37,19 +37,21 @@
 | 
				
			|||||||
                        <button type="submit" th:text="#{delete}">Delete</button>
 | 
					                        <button type="submit" th:text="#{delete}">Delete</button>
 | 
				
			||||||
                      </form>
 | 
					                      </form>
 | 
				
			||||||
                    </td>
 | 
					                    </td>
 | 
				
			||||||
 | 
					                    <td th:text="${user.authenticationType}"></td>
 | 
				
			||||||
                  </tr>
 | 
					                  </tr>
 | 
				
			||||||
                </tbody>
 | 
					                </tbody>
 | 
				
			||||||
              </table>
 | 
					              </table>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              <h2 th:text="#{adminUserSettings.addUser}">Add New User</h2>
 | 
					              <h2 th:text="#{adminUserSettings.addUser}">Add New User</h2>
 | 
				
			||||||
              <div th:if="${param.messageType != null and param.messageType.size() > 0 and (param.messageType[0] == 'usernameExists' or param.messageType[0] == 'invalidUsername')}" class="alert alert-danger">
 | 
					              <div th:if="${addMessage}" class="alert alert-danger">
 | 
				
			||||||
                <span th:if="${param.messageType[0] == 'usernameExists'}" th:text="#{usernameExistsMessage}">Default message if not found</span>
 | 
					                <span th:text="#{${addMessage}}">Default message if not found</span>
 | 
				
			||||||
                <span th:if="${param.messageType[0] == 'invalidUsername'}" th:text="#{invalidUsernameMessage}">Default message if not found</span>
 | 
					 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
              <form action="/api/v1/user/admin/saveUser" method="post">
 | 
					              <button class="btn btn-outline-info" data-toggle="tooltip" data-placement="auto" th:title="#{adminUserSettings.usernameInfo}" th:text="#{help}">Help</button>
 | 
				
			||||||
 | 
					              <form id="formsaveuser" action="/api/v1/user/admin/saveUser" method="post">
 | 
				
			||||||
                <div class="mb-3">
 | 
					                <div class="mb-3">
 | 
				
			||||||
                  <label for="username" th:text="#{username}">Username</label>
 | 
					                  <label for="username" th:text="#{username}">Username</label>
 | 
				
			||||||
                  <input type="text" class="form-control" name="username" pattern="[a-zA-Z0-9]+" th:title="#{adminUserSettings.usernameInfo}" required>
 | 
					                  <input type="text" class="form-control" name="username" id="username" th:title="#{adminUserSettings.usernameInfo}" required>
 | 
				
			||||||
 | 
					                  <span id="usernameError" style="display: none;" th:text="#{invalidUsernameMessage}">Invalid username!</span>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <div class="mb-3">
 | 
					                <div class="mb-3">
 | 
				
			||||||
                  <label for="password" th:text="#{password}">Password</label>
 | 
					                  <label for="password" th:text="#{password}">Password</label>
 | 
				
			||||||
@ -73,9 +75,8 @@
 | 
				
			|||||||
              <hr />
 | 
					              <hr />
 | 
				
			||||||
              <h2 th:text="#{adminUserSettings.changeUserRole}">Change User's Role</h2>
 | 
					              <h2 th:text="#{adminUserSettings.changeUserRole}">Change User's Role</h2>
 | 
				
			||||||
              <button class="btn btn-outline-info" data-toggle="tooltip" data-placement="auto" th:title="#{downgradeCurrentUserLongMessage}" th:text="#{help}">Help</button>
 | 
					              <button class="btn btn-outline-info" data-toggle="tooltip" data-placement="auto" th:title="#{downgradeCurrentUserLongMessage}" th:text="#{help}">Help</button>
 | 
				
			||||||
              <div th:if="${param.messageType != null and param.messageType.size() > 0 and (param.messageType[0] == 'userNotFound' or param.messageType[0] == 'downgradeCurrentUser')}" class="alert alert-danger">
 | 
					              <div th:if="${changeMessage}" class="alert alert-danger">
 | 
				
			||||||
                <span th:if="${param.messageType[0] == 'userNotFound'}" th:text="#{userNotFoundMessage}">Username not found</span>
 | 
					                <span th:text="#{${changeMessage}}">Default message if not found</span>
 | 
				
			||||||
                <span th:if="${param.messageType[0] == 'downgradeCurrentUser'}" th:text="#{downgradeCurrentUserMessage}">Cannot downgrade current user's role</span>
 | 
					 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
              <form action="/api/v1/user/admin/changeRole" method="post">
 | 
					              <form action="/api/v1/user/admin/changeRole" method="post">
 | 
				
			||||||
                <div class="mb-3">
 | 
					                <div class="mb-3">
 | 
				
			||||||
@ -101,9 +102,55 @@
 | 
				
			|||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <script th:inline="javascript">
 | 
					      <script th:inline="javascript">
 | 
				
			||||||
 | 
					        jQuery.validator.addMethod("usernamePattern", function(value, element) {
 | 
				
			||||||
 | 
					            return this.optional(element) || /^[a-zA-Z0-9][a-zA-Z0-9@._+-]*[a-zA-Z0-9]$|^(?=.{1,64}@)[A-Za-z0-9]+(\.[A-Za-z0-9_+.-]+)*@[^-][A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*(\.[A-Za-z]{2,})$/.test(value);
 | 
				
			||||||
 | 
					        }, /*[[#{invalidUsernameMessage}]]*/ "Invalid username format");
 | 
				
			||||||
        $(document).ready(function() {
 | 
					        $(document).ready(function() {
 | 
				
			||||||
          $('[data-toggle="tooltip"]').tooltip()
 | 
					          $('[data-toggle="tooltip"]').tooltip();
 | 
				
			||||||
        })
 | 
					
 | 
				
			||||||
 | 
					          $('#formsaveuser').validate({
 | 
				
			||||||
 | 
					            rules: {
 | 
				
			||||||
 | 
					              username: {
 | 
				
			||||||
 | 
					                required: true,
 | 
				
			||||||
 | 
					                usernamePattern: true
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					              password: {
 | 
				
			||||||
 | 
					                required: true
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					              messages: {
 | 
				
			||||||
 | 
					                  username: {
 | 
				
			||||||
 | 
					                      usernamePattern: /*[[#{invalidUsernameMessage}]]*/ "Invalid username format"
 | 
				
			||||||
 | 
					                  },
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					              errorPlacement: function(error, element) {
 | 
				
			||||||
 | 
					                  if (element.attr("name") === "username") {
 | 
				
			||||||
 | 
					                      $("#usernameError").text(error.text()).show();
 | 
				
			||||||
 | 
					                  } else {
 | 
				
			||||||
 | 
					                      error.insertAfter(element);
 | 
				
			||||||
 | 
					                  }
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					              success: function(label, element) {
 | 
				
			||||||
 | 
					                  if ($(element).attr("name") === "username") {
 | 
				
			||||||
 | 
					                      $("#usernameError").hide();
 | 
				
			||||||
 | 
					                  }
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          $('#username').on('input', function() {
 | 
				
			||||||
 | 
					              var usernameInput = $(this);
 | 
				
			||||||
 | 
					              var isValid = usernameInput[0].checkValidity();
 | 
				
			||||||
 | 
					              var errorSpan = $('#usernameError');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              if (isValid) {
 | 
				
			||||||
 | 
					                  usernameInput.removeClass('invalid').addClass('valid');
 | 
				
			||||||
 | 
					                  errorSpan.hide();
 | 
				
			||||||
 | 
					              } else {
 | 
				
			||||||
 | 
					                  usernameInput.removeClass('valid').addClass('invalid');
 | 
				
			||||||
 | 
					                  errorSpan.show();
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
      </script>
 | 
					      </script>
 | 
				
			||||||
      <th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
 | 
					      <th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 | 
				
			|||||||
@ -17,18 +17,9 @@
 | 
				
			|||||||
              <!-- User Settings Title -->
 | 
					              <!-- User Settings Title -->
 | 
				
			||||||
              <h2 class="text-center" th:text="#{changeCreds.header}">User Settings</h2>
 | 
					              <h2 class="text-center" th:text="#{changeCreds.header}">User Settings</h2>
 | 
				
			||||||
              <hr>
 | 
					              <hr>
 | 
				
			||||||
              <th:block th:if="${param.messageType != null and param.messageType.size() > 0}">
 | 
					              <th:block th:if="${messageType}">
 | 
				
			||||||
              <div th:if="${param.messageType[0] == 'notAuthenticated'}" class="alert alert-danger">
 | 
					              <div class="alert alert-danger">
 | 
				
			||||||
                <span th:text="#{notAuthenticatedMessage}">Default message if not found</span>
 | 
					                <span th:text="#{${messageType}}">Default message if not found</span>
 | 
				
			||||||
              </div>
 | 
					 | 
				
			||||||
              <div th:if="${param.messageType[0] == 'userNotFound'}" class="alert alert-danger">
 | 
					 | 
				
			||||||
                <span th:text="#{userNotFoundMessage}">Default message if not found</span>
 | 
					 | 
				
			||||||
              </div>
 | 
					 | 
				
			||||||
              <div th:if="${param.messageType[0] == 'incorrectPassword'}" class="alert alert-danger">
 | 
					 | 
				
			||||||
                <span th:text="#{incorrectPasswordMessage}">Default message if not found</span>
 | 
					 | 
				
			||||||
              </div>
 | 
					 | 
				
			||||||
              <div th:if="${param.messageType[0] == 'usernameExists'}" class="alert alert-danger">
 | 
					 | 
				
			||||||
                <span th:text="#{usernameExistsMessage}">Default message if not found</span>
 | 
					 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
              </th:block>
 | 
					              </th:block>
 | 
				
			||||||
              <!-- At the top of the user settings -->
 | 
					              <!-- At the top of the user settings -->
 | 
				
			||||||
 | 
				
			|||||||
@ -1,174 +1,170 @@
 | 
				
			|||||||
<th:block th:fragment="head">
 | 
					<th:block th:fragment="head">
 | 
				
			||||||
  <!-- Title -->
 | 
					    <!-- Title -->
 | 
				
			||||||
  <title th:text="${@appName} + (${title} != null and ${title} != '' ? ' - ' + ${title} : '')"></title>
 | 
					    <title th:text="${@appName} + (${title} != null and ${title} != '' ? ' - ' + ${title} : '')"></title>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <!-- Metadata -->
 | 
					    <!-- Metadata -->
 | 
				
			||||||
  <meta charset="utf-8">
 | 
					    <meta charset="utf-8">
 | 
				
			||||||
  <meta name="description" th:content="${@appName} + (${header} != null and ${header} != '' ? ' - ' + ${header} : '')">
 | 
					    <meta name="description" th:content="${@appName} + (${header} != null and ${header} != '' ? ' - ' + ${header} : '')">
 | 
				
			||||||
  <meta name="msapplication-TileColor" content="#2d89ef">
 | 
					    <meta name="msapplication-TileColor" content="#2d89ef">
 | 
				
			||||||
  <meta name="theme-color" content="#ffffff">
 | 
					    <meta name="theme-color" content="#ffffff">
 | 
				
			||||||
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
					    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <!-- Icons -->
 | 
					    <!-- Icons -->
 | 
				
			||||||
  <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png?v=2">
 | 
					    <link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon.png?v=2">
 | 
				
			||||||
  <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png?v=2">
 | 
					    <link rel="icon" type="image/png" sizes="32x32" href="favicon-32x32.png?v=2">
 | 
				
			||||||
  <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png?v=2">
 | 
					    <link rel="icon" type="image/png" sizes="16x16" href="favicon-16x16.png?v=2">
 | 
				
			||||||
  <link rel="manifest" href="/site.webmanifest?v=2">
 | 
					    <link rel="manifest" href="site.webmanifest?v=2">
 | 
				
			||||||
  <link rel="mask-icon" href="/safari-pinned-tab.svg?v=2" color="#ca2b2a">
 | 
					    <link rel="mask-icon" href="safari-pinned-tab.svg?v=2" color="#ca2b2a">
 | 
				
			||||||
  <link rel="shortcut icon" href="/favicon.ico?v=2">
 | 
					    <link rel="shortcut icon" href="favicon.ico?v=2">
 | 
				
			||||||
  <meta name="apple-mobile-web-app-title" content="Stirling PDF">
 | 
					    <meta name="apple-mobile-web-app-title" content="Stirling PDF">
 | 
				
			||||||
  <meta name="application-name" content="Stirling PDF">
 | 
					    <meta name="application-name" content="Stirling PDF">
 | 
				
			||||||
  <meta name="msapplication-TileColor" content="#00aba9">
 | 
					    <meta name="msapplication-TileColor" content="#00aba9">
 | 
				
			||||||
  <meta name="theme-color" content="#ffffff">
 | 
					    <meta name="theme-color" content="#ffffff">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <!-- jQuery -->
 | 
					    <!-- jQuery -->
 | 
				
			||||||
  <script src="js/thirdParty/jquery.min.js"></script>
 | 
					    <script src="js/thirdParty/jquery.min.js"></script>
 | 
				
			||||||
  <script src="js/thirdParty/jszip.min.js"></script>
 | 
					    <script src="js/thirdParty/jquery.validate.min.js"></script>
 | 
				
			||||||
 | 
					    <script src="js/thirdParty/jszip.min.js"></script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <!-- Bootstrap -->
 | 
					    <!-- Bootstrap -->
 | 
				
			||||||
  <script src="js/thirdParty/popper.min.js"></script>
 | 
					    <script src="js/thirdParty/popper.min.js"></script>
 | 
				
			||||||
  <script src="js/thirdParty/bootstrap.min.js"></script>
 | 
					    <script src="js/thirdParty/bootstrap.min.js"></script>
 | 
				
			||||||
  <link rel="stylesheet" href="css/bootstrap.min.css">
 | 
					    <link rel="stylesheet" href="css/bootstrap.min.css">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <!-- Bootstrap Icons -->
 | 
					    <!-- Bootstrap Icons -->
 | 
				
			||||||
  <link rel="stylesheet" href="css/bootstrap-icons.min.css">
 | 
					    <link rel="stylesheet" href="css/bootstrap-icons.min.css">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <!-- PDF.js -->
 | 
					    <!-- PDF.js -->
 | 
				
			||||||
  <script th:src="@{pdfjs/pdf.js}"></script>
 | 
					    <script th:src="@{pdfjs/pdf.js}"></script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <!-- PDF-Lib -->
 | 
					    <!-- PDF-Lib -->
 | 
				
			||||||
  <script src="js/thirdParty/pdf-lib.min.js"></script>
 | 
					    <script src="js/thirdParty/pdf-lib.min.js"></script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <!-- Custom -->
 | 
					    <!-- Custom -->
 | 
				
			||||||
  <link rel="stylesheet" href="css/general.css">
 | 
					    <link rel="stylesheet" href="css/general.css">
 | 
				
			||||||
  <link rel="stylesheet" th:href="@{css/theme/theme.css}">
 | 
					    <link rel="stylesheet" th:href="@{css/theme/theme.css}">
 | 
				
			||||||
  <link rel="stylesheet" th:href="@{css/theme/componentes.css}">
 | 
					    <link rel="stylesheet" th:href="@{css/theme/componentes.css}">
 | 
				
			||||||
  <link rel="stylesheet" th:href="@{css/theme/theme.light.css}" id="light-mode-styles">
 | 
					    <link rel="stylesheet" th:href="@{css/theme/theme.light.css}" id="light-mode-styles">
 | 
				
			||||||
  <link rel="stylesheet" th:href="@{css/theme/theme.dark.css}" id="dark-mode-styles">
 | 
					    <link rel="stylesheet" th:href="@{css/theme/theme.dark.css}" id="dark-mode-styles">
 | 
				
			||||||
  <link rel="stylesheet" th:href="@{css/rainbow-mode.css}" id="rainbow-mode-styles" disabled>
 | 
					    <link rel="stylesheet" th:href="@{css/rainbow-mode.css}" id="rainbow-mode-styles" disabled>
 | 
				
			||||||
  <link rel="stylesheet" href="css/tab-container.css">
 | 
					    <link rel="stylesheet" href="css/tab-container.css">
 | 
				
			||||||
  <link rel="stylesheet" href="css/navbar.css">
 | 
					    <link rel="stylesheet" href="css/navbar.css">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <link rel="stylesheet" th:href="@{/css/error.css}" th:if="${error}">
 | 
					    <link rel="stylesheet" th:href="@{/css/error.css}" th:if="${error}">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <link rel="stylesheet" href="css/home.css" th:if="${currentPage == 'home'}">
 | 
					    <link rel="stylesheet" href="css/home.css" th:if="${currentPage == 'home'}">
 | 
				
			||||||
  <link rel="stylesheet" href="css/account.css" th:if="${currentPage == 'account'}">
 | 
					    <link rel="stylesheet" href="css/account.css" th:if="${currentPage == 'account'}">
 | 
				
			||||||
  <link rel="stylesheet" href="css/licenses.css" th:if="${currentPage == 'licenses'}">
 | 
					    <link rel="stylesheet" href="css/licenses.css" th:if="${currentPage == 'licenses'}">
 | 
				
			||||||
  <link rel="stylesheet" href="css/multi-tool.css" th:if="${currentPage == 'multi-tool'}">
 | 
					    <link rel="stylesheet" href="css/multi-tool.css" th:if="${currentPage == 'multi-tool'}">
 | 
				
			||||||
  <link rel="stylesheet" href="css/rotate-pdf.css" th:if="${currentPage == 'rotate-pdf'}">
 | 
					    <link rel="stylesheet" href="css/rotate-pdf.css" th:if="${currentPage == 'rotate-pdf'}">
 | 
				
			||||||
  <link rel="stylesheet" href="css/stamp.css" th:if="${currentPage == 'stamp'}">
 | 
					    <link rel="stylesheet" href="css/stamp.css" th:if="${currentPage == 'stamp'}">
 | 
				
			||||||
  <link rel="stylesheet" href="css/fileSelect.css">
 | 
					    <link rel="stylesheet" href="css/fileSelect.css">
 | 
				
			||||||
  <link rel="stylesheet" href="css/footer.css">
 | 
					    <link rel="stylesheet" href="css/footer.css">
 | 
				
			||||||
<script src="js/thirdParty/fontfaceobserver.standalone.js"></script>
 | 
					    <script src="js/thirdParty/fontfaceobserver.standalone.js"></script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <!-- Google MD Icons -->
 | 
					    <!-- Google MD Icons -->
 | 
				
			||||||
  <link rel="stylesheet" href="css/theme/font.css">
 | 
					    <link rel="stylesheet" href="css/theme/font.css">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <!-- Help Modal -->
 | 
					    <!-- Help Modal -->
 | 
				
			||||||
  <link rel="stylesheet" href="css/errorBanner.css">
 | 
					    <link rel="stylesheet" href="css/errorBanner.css">
 | 
				
			||||||
 | 
					 | 
				
			||||||
  <script src="js/cacheFormInputs.js"></script>
 | 
					 | 
				
			||||||
  <script src="js/tab-container.js"></script>
 | 
					 | 
				
			||||||
  <script src="js/darkmode.js"></script>
 | 
					 | 
				
			||||||
</th:block>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <script src="js/cacheFormInputs.js"></script>
 | 
				
			||||||
 | 
					    <script src="js/tab-container.js"></script>
 | 
				
			||||||
 | 
					    <script src="js/darkmode.js"></script>
 | 
				
			||||||
 | 
					  </th:block>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<th:block th:fragment="game">
 | 
					<th:block th:fragment="game">
 | 
				
			||||||
  <dialog id="game-container-wrapper" class="game-container-wrapper" data-bs-modal>
 | 
					    <dialog id="game-container-wrapper" class="game-container-wrapper" data-bs-modal>
 | 
				
			||||||
    <script th:inline="javascript">
 | 
					      <script th:inline="javascript">
 | 
				
			||||||
      console.log("loaded game");
 | 
					        console.log("loaded game");
 | 
				
			||||||
      $(document).ready(function () {
 | 
					        $(document).ready(function() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Find the file input within the form
 | 
					           // Find the file input within the form
 | 
				
			||||||
        var fileInput = $('input[type="file"]');
 | 
					          var fileInput = $('input[type="file"]');
 | 
				
			||||||
 | 
					          
 | 
				
			||||||
 | 
					          // Find the closest enclosing form of the file input
 | 
				
			||||||
 | 
					          var form = fileInput.closest('form');
 | 
				
			||||||
 | 
					          
 | 
				
			||||||
 | 
					          // Find the submit button within the form
 | 
				
			||||||
 | 
					          var submitButton = form.find('button[type="submit"], input[type="submit"]');
 | 
				
			||||||
 | 
					          
 | 
				
			||||||
 | 
					          const boredWaitingText = /*[[#{bored}]]*/ 'Bored Waiting?';
 | 
				
			||||||
 | 
					          const downloadCompleteText = /*[[#{downloadComplete}]]*/ 'Download Complete';
 | 
				
			||||||
 | 
					          window.downloadCompleteText = downloadCompleteText;
 | 
				
			||||||
 | 
					          // Create the 'show-game-btn' button
 | 
				
			||||||
 | 
					          var gameButton = $('<button type="button" class="btn btn-primary" id="show-game-btn" style="display:none;">' + boredWaitingText + '</button><br><br>');
 | 
				
			||||||
 | 
					          
 | 
				
			||||||
 | 
					          // Insert the 'show-game-btn' just above the submit button
 | 
				
			||||||
 | 
					          submitButton.before(gameButton);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Find the closest enclosing form of the file input
 | 
					          function loadGameScript(callback) {
 | 
				
			||||||
        var form = fileInput.closest('form');
 | 
					            console.log('loadGameScript called');
 | 
				
			||||||
 | 
					            const script = document.createElement('script');
 | 
				
			||||||
        // Find the submit button within the form
 | 
					            script.src = 'js/game.js';
 | 
				
			||||||
        var submitButton = form.find('button[type="submit"], input[type="submit"]');
 | 
					            script.onload = callback;
 | 
				
			||||||
 | 
					            document.body.appendChild(script);
 | 
				
			||||||
        const boredWaitingText = /*[[#{bored}]]*/ 'Bored Waiting?';
 | 
					 | 
				
			||||||
        const downloadCompleteText = /*[[#{downloadComplete}]]*/ 'Download Complete';
 | 
					 | 
				
			||||||
        window.downloadCompleteText = downloadCompleteText;
 | 
					 | 
				
			||||||
        // Create the 'show-game-btn' button
 | 
					 | 
				
			||||||
        var gameButton = $('<button type="button" class="btn btn-primary" id="show-game-btn" style="display:none;">' + boredWaitingText + '</button><br><br>');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Insert the 'show-game-btn' just above the submit button
 | 
					 | 
				
			||||||
        submitButton.before(gameButton);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        function loadGameScript(callback) {
 | 
					 | 
				
			||||||
          console.log('loadGameScript called');
 | 
					 | 
				
			||||||
          const script = document.createElement('script');
 | 
					 | 
				
			||||||
          script.src = 'js/game.js';
 | 
					 | 
				
			||||||
          script.onload = callback;
 | 
					 | 
				
			||||||
          document.body.appendChild(script);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        let gameScriptLoaded = false;
 | 
					 | 
				
			||||||
        const gameDialog = document.getElementById('game-container-wrapper');
 | 
					 | 
				
			||||||
        $('#show-game-btn').on('click', function () {
 | 
					 | 
				
			||||||
          console.log('Show game button clicked');
 | 
					 | 
				
			||||||
          if (!gameScriptLoaded) {
 | 
					 | 
				
			||||||
            console.log('Show game button load');
 | 
					 | 
				
			||||||
            loadGameScript(function () {
 | 
					 | 
				
			||||||
              console.log('Game script loaded');
 | 
					 | 
				
			||||||
              window.initializeGame();
 | 
					 | 
				
			||||||
              gameScriptLoaded = true;
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
          } else {
 | 
					 | 
				
			||||||
            window.resetGame();
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
          gameDialog.showModal();
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        gameDialog.addEventListener("click", e => {
 | 
					 | 
				
			||||||
          const dialogDimensions = gameDialog.getBoundingClientRect()
 | 
					 | 
				
			||||||
          if (
 | 
					 | 
				
			||||||
            e.clientX < dialogDimensions.left ||
 | 
					 | 
				
			||||||
            e.clientX > dialogDimensions.right ||
 | 
					 | 
				
			||||||
            e.clientY < dialogDimensions.top ||
 | 
					 | 
				
			||||||
            e.clientY > dialogDimensions.bottom
 | 
					 | 
				
			||||||
          ) {
 | 
					 | 
				
			||||||
            gameDialog.close();
 | 
					 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					          let gameScriptLoaded = false;
 | 
				
			||||||
 | 
					          const gameDialog = document.getElementById('game-container-wrapper');
 | 
				
			||||||
 | 
					          $('#show-game-btn').on('click', function() {
 | 
				
			||||||
 | 
					            console.log('Show game button clicked');
 | 
				
			||||||
 | 
					            if (!gameScriptLoaded) {
 | 
				
			||||||
 | 
					              console.log('Show game button load');
 | 
				
			||||||
 | 
					              loadGameScript(function() {
 | 
				
			||||||
 | 
					                console.log('Game script loaded');
 | 
				
			||||||
 | 
					                window.initializeGame();
 | 
				
			||||||
 | 
					                gameScriptLoaded = true;
 | 
				
			||||||
 | 
					              });
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					              window.resetGame();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            gameDialog.showModal();
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					          gameDialog.addEventListener("click", e => {
 | 
				
			||||||
 | 
					            const dialogDimensions = gameDialog.getBoundingClientRect()
 | 
				
			||||||
 | 
					            if (
 | 
				
			||||||
 | 
					              e.clientX < dialogDimensions.left ||
 | 
				
			||||||
 | 
					              e.clientX > dialogDimensions.right ||
 | 
				
			||||||
 | 
					              e.clientY < dialogDimensions.top ||
 | 
				
			||||||
 | 
					              e.clientY > dialogDimensions.bottom
 | 
				
			||||||
 | 
					            ) {
 | 
				
			||||||
 | 
					              gameDialog.close();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
      })
 | 
					      </script>
 | 
				
			||||||
    </script>
 | 
					      <div id="game-container">
 | 
				
			||||||
    <div id="game-container">
 | 
					        <div id="lives">Lives: 3</div>
 | 
				
			||||||
      <div id="lives">Lives: 3</div>
 | 
					        <div id="score">Score: 0</div>
 | 
				
			||||||
      <div id="score">Score: 0</div>
 | 
					        <div id="high-score">High Score: 0</div>
 | 
				
			||||||
      <div id="high-score">High Score: 0</div>
 | 
					        <div id="level">Level: 1</div>
 | 
				
			||||||
      <div id="level">Level: 1</div>
 | 
					        <img src="favicon.svg" class="player" id="player" alt="favicon">
 | 
				
			||||||
      <img src="favicon.svg" class="player" id="player" alt="favicon">
 | 
					      </div>
 | 
				
			||||||
    </div>
 | 
					      <link rel="stylesheet" href="css/game.css">
 | 
				
			||||||
    <link rel="stylesheet" href="css/game.css">
 | 
					    </dialog>
 | 
				
			||||||
  </dialog>
 | 
					 | 
				
			||||||
</th:block>
 | 
					</th:block>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<th:block th:fragment="fileSelector(name, multiple)"
 | 
					<th:block th:fragment="fileSelector(name, multiple)" th:with="accept=${accept} ?: '*/*', inputText=${inputText} ?: #{pdfPrompt}, remoteCall=${remoteCall} ?: true, notRequired=${notRequired} ?: false">
 | 
				
			||||||
  th:with="accept=${accept} ?: '*/*', inputText=${inputText} ?: #{pdfPrompt}, remoteCall=${remoteCall} ?: true, notRequired=${notRequired} ?: false">
 | 
					                <script th:inline="javascript">
 | 
				
			||||||
  <script th:inline="javascript">
 | 
					                  const pdfPasswordPrompt = /*[[#{error.pdfPassword}]]*/ '';
 | 
				
			||||||
    const pdfPasswordPrompt = /*[[#{error.pdfPassword}]]*/ '';
 | 
					                  const multiple = [[${multiple}]] || false;
 | 
				
			||||||
    const multiple = [[${ multiple }]] || false;
 | 
					                  const remoteCall = [[${remoteCall}]] || true;
 | 
				
			||||||
    const remoteCall = [[${ remoteCall }]] || true;
 | 
					                </script>
 | 
				
			||||||
  </script>
 | 
					                <script src="js/downloader.js"></script>
 | 
				
			||||||
  <script src="js/downloader.js"></script>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <div class="custom-file-chooser"
 | 
					                <div class="custom-file-chooser" th:attr="data-bs-unique-id=${name}, data-bs-element-id=${name+'-input'}, data-bs-files-selected=#{filesSelected}, data-bs-pdf-prompt=#{pdfPrompt}">
 | 
				
			||||||
    th:attr="data-bs-unique-id=${name}, data-bs-element-id=${name+'-input'}, data-bs-files-selected=#{filesSelected}, data-bs-pdf-prompt=#{pdfPrompt}">
 | 
					                  <div class="mb-3">
 | 
				
			||||||
    <div class="mb-3">
 | 
					                    <input type="file" class="form-control" th:name="${name}" th:id="${name}+'-input'" th:accept="${accept}" multiple th:required="${notRequired} ? null : 'required'">
 | 
				
			||||||
      <input type="file" class="form-control" th:name="${name}" th:id="${name}+'-input'" th:accept="${accept}" multiple
 | 
					                  </div>
 | 
				
			||||||
        th:required="${notRequired} ? null : 'required'">
 | 
					                  <div class="selected-files"></div>
 | 
				
			||||||
    </div>
 | 
					                </div>
 | 
				
			||||||
    <div class="selected-files"></div>
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <div class="progressBarContainer" style="display: none; position: relative;">
 | 
					                <div class="progressBarContainer" style="display: none; position: relative;">
 | 
				
			||||||
    <div class="progress" style="height: 1rem;">
 | 
					                  <div class="progress" style="height: 1rem;">
 | 
				
			||||||
      <div class="progressBar progress-bar progress-bar-striped progress-bar-animated bg-success" role="progressbar"
 | 
					                    <div class="progressBar progress-bar progress-bar-striped progress-bar-animated bg-success" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%;">
 | 
				
			||||||
        aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%;">
 | 
					                      <span class="visually-hidden">Loading...</span>
 | 
				
			||||||
        <span class="visually-hidden">Loading...</span>
 | 
					                    </div>
 | 
				
			||||||
      </div>
 | 
					                  </div>
 | 
				
			||||||
    </div>
 | 
					                </div>
 | 
				
			||||||
  </div>
 | 
					                <script src="js/fileInput.js"></script>
 | 
				
			||||||
  <script src="js/fileInput.js"></script>
 | 
					 | 
				
			||||||
</th:block>
 | 
					</th:block>
 | 
				
			||||||
@ -112,25 +112,32 @@
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        </script>
 | 
					        </script>
 | 
				
			||||||
        <div th:if="${logoutMessage}" class="alert alert-success" th:text="${logoutMessage}"></div>
 | 
					        <div class="text-center">
 | 
				
			||||||
        <div th:if="${param.messageType != null and param.messageType.size() > 0 and param.messageType[0] == 'credsUpdated'}" class="alert alert-success">
 | 
					          <img class="mb-4" src="favicon.svg?v=2" alt="favicon" width="144" height="144">
 | 
				
			||||||
          <span th:text="#{changedCredsMessage}">Default message if not found</span>
 | 
					
 | 
				
			||||||
        </div>
 | 
					          <h1 class="h1 mb-3 fw-normal" th:text="${@appName}">Stirling-PDF</h1>
 | 
				
			||||||
        <form th:action="@{login}" method="post">
 | 
					          <div th:if="${oAuth2Enabled}">
 | 
				
			||||||
          <div class="text-center">
 | 
					 | 
				
			||||||
            <img class="mb-4" src="favicon.svg?v=2" alt="" width="144" height="144">
 | 
					 | 
				
			||||||
            <h1 class="h1 mb-3 fw-normal" th:text="${@appName}">Stirling-PDF</h1>
 | 
					 | 
				
			||||||
            <div th:if="${oAuth2Enabled}">
 | 
					 | 
				
			||||||
            <a href="oauth2/authorization/oidc" class="w-100 btn btn-lg btn-primary" th:text="#{login.ssoSignIn}">Login Via SSO</a>
 | 
					            <a href="oauth2/authorization/oidc" class="w-100 btn btn-lg btn-primary" th:text="#{login.ssoSignIn}">Login Via SSO</a>
 | 
				
			||||||
            <div class="text-danger text-center">
 | 
					            <br>
 | 
				
			||||||
              <div th:if="${error == 'oauth2AutoCreateDisabled'}" th:text="#{login.oauth2AutoCreateDisabled}">OAUTH2 Auto-Create User Disabled.</div>
 | 
					            <br>
 | 
				
			||||||
 | 
					            <div th:if="${erroroauth}" class="alert alert-danger text-center">
 | 
				
			||||||
 | 
					              <div th:if="${erroroauth}" th:text="#{${erroroauth}}">OAuth2: Error Message</div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <hr />
 | 
					            <hr />
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
            <h2 class="h5 mb-3 fw-normal" th:text="#{login.signinTitle}">Please sign in</h2>
 | 
					
 | 
				
			||||||
 | 
					          <div th:if="${error}" class="alert alert-danger text-danger text-center">
 | 
				
			||||||
 | 
					            <div th:if="${error}" th:text="#{${error}}">OAuth2: Error Message</div>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
 | 
					          <div class="text-danger text-center">
 | 
				
			||||||
 | 
					            <div th:if="${logoutMessage}" class="alert alert-success" th:text="${logoutMessage}"></div>
 | 
				
			||||||
 | 
					            <div th:if="${messageType}" class="alert alert-success">
 | 
				
			||||||
 | 
					              <span th:text="#{${messageType}}">Default message if not found</span>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <form th:action="@{login}" method="post">
 | 
				
			||||||
 | 
					          <h2 class="h5 mb-3 fw-normal" th:text="#{login.signinTitle}">Please sign in</h2>
 | 
				
			||||||
          <div class="form-floating">
 | 
					          <div class="form-floating">
 | 
				
			||||||
            <input type="text" class="form-control" id="username" name="username" placeholder="admin">
 | 
					            <input type="text" class="form-control" id="username" name="username" placeholder="admin">
 | 
				
			||||||
            <label for="username" th:text="#{username}">Username</label>
 | 
					            <label for="username" th:text="#{username}">Username</label>
 | 
				
			||||||
@ -155,15 +162,11 @@
 | 
				
			|||||||
            <div class="dropdown-menu" aria-labelledby="languageDropdown">
 | 
					            <div class="dropdown-menu" aria-labelledby="languageDropdown">
 | 
				
			||||||
              <!-- Here's where the fragment will be included -->
 | 
					              <!-- Here's where the fragment will be included -->
 | 
				
			||||||
              <div class="scrollable-y">
 | 
					              <div class="scrollable-y">
 | 
				
			||||||
                <th:block th:replace="~{fragments/languages :: langs}"></th:block>
 | 
					              <th:block th:replace="~{fragments/languages :: langs}"></th:block>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div class="text-danger text-center">
 | 
					 | 
				
			||||||
          <div th:if="${error == 'badcredentials'}" th:text="#{login.invalid}">Invalid username or password.</div>
 | 
					 | 
				
			||||||
          <div th:if="${error == 'locked'}" th:text="#{login.locked}">Your account has been locked. </div>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
      </main>
 | 
					      </main>
 | 
				
			||||||
      <th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
 | 
					      <th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user