redact stuff, login page, lang and remember me

This commit is contained in:
Anthony Stirling 2023-08-25 23:39:18 +01:00
parent cd0e1a3962
commit 41bd801e0d
12 changed files with 324 additions and 209 deletions

View file

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

View file

@ -15,7 +15,11 @@ 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.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
@Configuration @Configuration
public class SecurityConfiguration { public class SecurityConfiguration {
@ -43,7 +47,7 @@ public class SecurityConfiguration {
if(loginEnabledValue) { if(loginEnabledValue) {
http.csrf().disable(); http.csrf(csrf -> csrf.disable());
http http
.formLogin(formLogin -> formLogin .formLogin(formLogin -> formLogin
.loginPage("/login") .loginPage("/login")
@ -55,7 +59,11 @@ public class SecurityConfiguration {
.logoutRequestMatcher(new AntPathRequestMatcher("/logout")) .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login?logout=true") .logoutSuccessUrl("/login?logout=true")
.invalidateHttpSession(true) // Invalidate session .invalidateHttpSession(true) // Invalidate session
.deleteCookies("JSESSIONID") .deleteCookies("JSESSIONID", "remember-me")
).rememberMe(rememberMeConfigurer -> rememberMeConfigurer // Use the configurator directly
.key("uniqueAndSecret")
.tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(1209600) // 2 weeks
) )
.authorizeHttpRequests(authz -> authz .authorizeHttpRequests(authz -> authz
.requestMatchers(req -> req.getRequestURI().startsWith("/login") || req.getRequestURI().endsWith(".svg") || req.getRequestURI().startsWith("/register") || req.getRequestURI().startsWith("/error") || req.getRequestURI().startsWith("/images/") || req.getRequestURI().startsWith("/public/") || req.getRequestURI().startsWith("/css/") || req.getRequestURI().startsWith("/js/")) .requestMatchers(req -> req.getRequestURI().startsWith("/login") || req.getRequestURI().endsWith(".svg") || req.getRequestURI().startsWith("/register") || req.getRequestURI().startsWith("/error") || req.getRequestURI().startsWith("/images/") || req.getRequestURI().startsWith("/public/") || req.getRequestURI().startsWith("/css/") || req.getRequestURI().startsWith("/js/"))
@ -65,8 +73,7 @@ public class SecurityConfiguration {
.userDetailsService(userDetailsService) .userDetailsService(userDetailsService)
.authenticationProvider(authenticationProvider()); .authenticationProvider(authenticationProvider());
} else { } else {
http http.csrf(csrf -> csrf.disable())
.csrf().disable()
.authorizeHttpRequests(authz -> authz .authorizeHttpRequests(authz -> authz
.anyRequest().permitAll() .anyRequest().permitAll()
); );
@ -84,6 +91,11 @@ public class SecurityConfiguration {
return authProvider; return authProvider;
} }
@Bean
public PersistentTokenRepository persistentTokenRepository() {
return new JPATokenRepositoryImpl();
}
} }

View file

@ -0,0 +1,60 @@
package stirling.software.SPDF.model;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.util.Date;
@Entity
@Table(name = "persistent_logins")
public class PersistentLogin {
@Id
@Column(name = "series")
private String series;
@Column(name = "username", length = 64, nullable = false)
private String username;
@Column(name = "token", length = 64, nullable = false)
private String token;
@Column(name = "last_used", nullable = false)
private Date lastUsed;
public String getSeries() {
return series;
}
public void setSeries(String series) {
this.series = series;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public Date getLastUsed() {
return lastUsed;
}
public void setLastUsed(Date lastUsed) {
this.lastUsed = lastUsed;
}
// Getters, setters, etc.
}

View file

@ -0,0 +1,53 @@
package stirling.software.SPDF.repository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import stirling.software.SPDF.model.PersistentLogin;
import stirling.software.SPDF.repository.PersistentLoginRepository;
import java.util.Date;
public class JPATokenRepositoryImpl implements PersistentTokenRepository {
@Autowired
private PersistentLoginRepository persistentLoginRepository;
@Override
public void createNewToken(PersistentRememberMeToken token) {
PersistentLogin newToken = new PersistentLogin();
newToken.setSeries(token.getSeries());
newToken.setUsername(token.getUsername());
newToken.setToken(token.getTokenValue());
newToken.setLastUsed(token.getDate());
persistentLoginRepository.save(newToken);
}
@Override
public void updateToken(String series, String tokenValue, Date lastUsed) {
PersistentLogin existingToken = persistentLoginRepository.findById(series).orElse(null);
if (existingToken != null) {
existingToken.setToken(tokenValue);
existingToken.setLastUsed(lastUsed);
persistentLoginRepository.save(existingToken);
}
}
@Override
public PersistentRememberMeToken getTokenForSeries(String seriesId) {
PersistentLogin token = persistentLoginRepository.findById(seriesId).orElse(null);
if (token != null) {
return new PersistentRememberMeToken(token.getUsername(), token.getSeries(), token.getToken(), token.getLastUsed());
}
return null;
}
@Override
public void removeUserTokens(String username) {
for (PersistentLogin token : persistentLoginRepository.findAll()) {
if (token.getUsername().equals(username)) {
persistentLoginRepository.delete(token);
}
}
}
}

View file

@ -0,0 +1,7 @@
package stirling.software.SPDF.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import stirling.software.SPDF.model.PersistentLogin;
public interface PersistentLoginRepository extends JpaRepository<PersistentLogin, String> {
}

View file

@ -299,6 +299,10 @@ home.showJS.title=Show Javascript
home.showJS.desc=Searches and displays any JS injected into a PDF home.showJS.desc=Searches and displays any JS injected into a PDF
showJS.tags=JS showJS.tags=JS
home.autoRedact.title=Auto Redact
home.autoRedact.desc=Auto Redacts(Blacks out) text in a PDF based on input text
showJS.tags=Redact,Hide,black out,black,marker,hidden
########################### ###########################
# # # #
# WEB PAGES # # WEB PAGES #
@ -307,6 +311,14 @@ showJS.tags=JS
#auto-redact #auto-redact
autoRedact.title=Auto Redact autoRedact.title=Auto Redact
autoRedact.header=Auto Redact autoRedact.header=Auto Redact
autoRedact.textsToRedactLabel=Text to Redact (line-separated)
autoRedact.textsToRedactPlaceholder=e.g. \nConfidential \nTop-Secret
autoRedact.useRegexLabel=Use Regex
autoRedact.wholeWordSearchLabel=Whole Word Search
autoRedact.customPaddingLabel=Custom Extra Padding
autoRedact.convertPDFToImageLabel=Convert PDF to Image
autoRedact.submitButton=Submit
#showJS #showJS
showJS.title=Show Javascript showJS.title=Show Javascript

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-eraser-fill" viewBox="0 0 16 16">
<path d="M8.086 2.207a2 2 0 0 1 2.828 0l3.879 3.879a2 2 0 0 1 0 2.828l-5.5 5.5A2 2 0 0 1 7.879 15H5.12a2 2 0 0 1-1.414-.586l-2.5-2.5a2 2 0 0 1 0-2.828l6.879-6.879zm.66 11.34L3.453 8.254 1.914 9.793a1 1 0 0 0 0 1.414l2.5 2.5a1 1 0 0 0 .707.293H7.88a1 1 0 0 0 .707-.293l.16-.16z"/>
</svg>

After

Width:  |  Height:  |  Size: 418 B

View file

@ -11,7 +11,7 @@
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="languageDropdown" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <a class="nav-link dropdown-toggle" href="#" id="languageDropdown" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<svg xmsns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-globe2" viewBox="0 0 20 20"> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-globe2" viewBox="0 0 20 20">
<path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm7.5-6.923c-.67.204-1.335.82-1.887 1.855-.143.268-.276.56-.395.872.705.157 1.472.257 2.282.287V1.077zM4.249 3.539c.142-.384.304-.744.481-1.078a6.7 6.7 0 0 1 .597-.933A7.01 7.01 0 0 0 3.051 3.05c.362.184.763.349 1.198.49zM3.509 7.5c.036-1.07.188-2.087.436-3.008a9.124 9.124 0 0 1-1.565-.667A6.964 6.964 0 0 0 1.018 7.5h2.49zm1.4-2.741a12.344 12.344 0 0 0-.4 2.741H7.5V5.091c-.91-.03-1.783-.145-2.591-.332zM8.5 5.09V7.5h2.99a12.342 12.342 0 0 0-.399-2.741c-.808.187-1.681.301-2.591.332zM4.51 8.5c.035.987.176 1.914.399 2.741A13.612 13.612 0 0 1 7.5 10.91V8.5H4.51zm3.99 0v2.409c.91.03 1.783.145 2.591.332.223-.827.364-1.754.4-2.741H8.5zm-3.282 3.696c.12.312.252.604.395.872.552 1.035 1.218 1.65 1.887 1.855V11.91c-.81.03-1.577.13-2.282.287zm.11 2.276a6.696 6.696 0 0 1-.598-.933 8.853 8.853 0 0 1-.481-1.079 8.38 8.38 0 0 0-1.198.49 7.01 7.01 0 0 0 2.276 1.522zm-1.383-2.964A13.36 13.36 0 0 1 3.508 8.5h-2.49a6.963 6.963 0 0 0 1.362 3.675c.47-.258.995-.482 1.565-.667zm6.728 2.964a7.009 7.009 0 0 0 2.275-1.521 8.376 8.376 0 0 0-1.197-.49 8.853 8.853 0 0 1-.481 1.078 6.688 6.688 0 0 1-.597.933zM8.5 11.909v3.014c.67-.204 1.335-.82 1.887-1.855.143-.268.276-.56.395-.872A12.63 12.63 0 0 0 8.5 11.91zm3.555-.401c.57.185 1.095.409 1.565.667A6.963 6.963 0 0 0 14.982 8.5h-2.49a13.36 13.36 0 0 1-.437 3.008zM14.982 7.5a6.963 6.963 0 0 0-1.362-3.675c-.47.258-.995.482-1.565.667.248.92.4 1.938.437 3.008h2.49zM11.27 2.461c.177.334.339.694.482 1.078a8.368 8.368 0 0 0 1.196-.49 7.01 7.01 0 0 0-2.275-1.52c.218.283.418.597.597.932zm-.488 1.343a7.765 7.765 0 0 0-.395-.872C9.835 1.897 9.17 1.282 8.5 1.077V4.09c.81-.03 1.577-.13 2.282-.287z"/> <path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm7.5-6.923c-.67.204-1.335.82-1.887 1.855-.143.268-.276.56-.395.872.705.157 1.472.257 2.282.287V1.077zM4.249 3.539c.142-.384.304-.744.481-1.078a6.7 6.7 0 0 1 .597-.933A7.01 7.01 0 0 0 3.051 3.05c.362.184.763.349 1.198.49zM3.509 7.5c.036-1.07.188-2.087.436-3.008a9.124 9.124 0 0 1-1.565-.667A6.964 6.964 0 0 0 1.018 7.5h2.49zm1.4-2.741a12.344 12.344 0 0 0-.4 2.741H7.5V5.091c-.91-.03-1.783-.145-2.591-.332zM8.5 5.09V7.5h2.99a12.342 12.342 0 0 0-.399-2.741c-.808.187-1.681.301-2.591.332zM4.51 8.5c.035.987.176 1.914.399 2.741A13.612 13.612 0 0 1 7.5 10.91V8.5H4.51zm3.99 0v2.409c.91.03 1.783.145 2.591.332.223-.827.364-1.754.4-2.741H8.5zm-3.282 3.696c.12.312.252.604.395.872.552 1.035 1.218 1.65 1.887 1.855V11.91c-.81.03-1.577.13-2.282.287zm.11 2.276a6.696 6.696 0 0 1-.598-.933 8.853 8.853 0 0 1-.481-1.079 8.38 8.38 0 0 0-1.198.49 7.01 7.01 0 0 0 2.276 1.522zm-1.383-2.964A13.36 13.36 0 0 1 3.508 8.5h-2.49a6.963 6.963 0 0 0 1.362 3.675c.47-.258.995-.482 1.565-.667zm6.728 2.964a7.009 7.009 0 0 0 2.275-1.521 8.376 8.376 0 0 0-1.197-.49 8.853 8.853 0 0 1-.481 1.078 6.688 6.688 0 0 1-.597.933zM8.5 11.909v3.014c.67-.204 1.335-.82 1.887-1.855.143-.268.276-.56.395-.872A12.63 12.63 0 0 0 8.5 11.91zm3.555-.401c.57.185 1.095.409 1.565.667A6.963 6.963 0 0 0 14.982 8.5h-2.49a13.36 13.36 0 0 1-.437 3.008zM14.982 7.5a6.963 6.963 0 0 0-1.362-3.675c-.47.258-.995.482-1.565.667.248.92.4 1.938.437 3.008h2.49zM11.27 2.461c.177.334.339.694.482 1.078a8.368 8.368 0 0 0 1.196-.49 7.01 7.01 0 0 0-2.275-1.52c.218.283.418.597.597.932zm-.488 1.343a7.765 7.765 0 0 0-.395-.872C9.835 1.897 9.17 1.282 8.5 1.077V4.09c.81-.03 1.577-.13 2.282-.287z"/>
</svg> </svg>
</a> </a>
@ -37,7 +37,7 @@
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="eu_ES"> <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="eu_ES">
<img src="images/flags/eu.svg" alt="icon" width="20" height="15"> Euskara <img src="images/flags/eu.svg" alt="icon" width="20" height="15"> Euskara
</a> </a>
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="es_ES"> <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="es_ES">
<img src="images/flags/es.svg" alt="icon" width="20" height="15"> Español <img src="images/flags/es.svg" alt="icon" width="20" height="15"> Español
</a> </a>
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="fr_FR"> <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="fr_FR">
@ -49,8 +49,8 @@
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="nl_NL"> <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="nl_NL">
<img src="images/flags/nl.svg" alt="icon" width="20" height="15"> Nederlands <img src="images/flags/nl.svg" alt="icon" width="20" height="15"> Nederlands
</a> </a>
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="ps_PL"> <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="pl_PL">
<img src="images/flags/ps.svg" alt="icon" width="20" height="15"> Polski <img src="images/flags/pl.svg" alt="icon" width="20" height="15"> Polski
</a> </a>
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="pt_BR"> <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="pt_BR">
<img src="images/flags/pt_br.svg" alt="icon" width="20" height="15"> Português (BR) <img src="images/flags/pt_br.svg" alt="icon" width="20" height="15"> Português (BR)

View file

@ -104,7 +104,7 @@
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('add-watermark', 'images/droplet.svg', 'home.watermark.title', 'home.watermark.desc', 'watermark.tags')}"></div> <div th:replace="~{fragments/navbarEntry :: navbarEntry ('add-watermark', 'images/droplet.svg', 'home.watermark.title', 'home.watermark.desc', 'watermark.tags')}"></div>
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('cert-sign', 'images/award.svg', 'home.certSign.title', 'home.certSign.desc', 'certSign.tags')}"></div> <div th:replace="~{fragments/navbarEntry :: navbarEntry ('cert-sign', 'images/award.svg', 'home.certSign.title', 'home.certSign.desc', 'certSign.tags')}"></div>
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('sanitize-pdf', 'images/sanitize.svg', 'home.sanitizePdf.title', 'home.sanitizePdf.desc', 'sanitizePdf.tags')}"></div> <div th:replace="~{fragments/navbarEntry :: navbarEntry ('sanitize-pdf', 'images/sanitize.svg', 'home.sanitizePdf.title', 'home.sanitizePdf.desc', 'sanitizePdf.tags')}"></div>
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('auto-redact', 'images/eraser-fill.svg', 'home.autoRedact.title', 'home.autoRedact.desc', 'autoRedact.tags')}"></div>
</div> </div>
</li> </li>
@ -137,7 +137,7 @@
</ul> </ul>
<ul class="navbar-nav ms-auto flex-nowrap"> <ul class="navbar-nav flex-nowrap">
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<img class="navbar-icon" src="images/star.svg" alt="icon" width="24" height="24"> <img class="navbar-icon" src="images/star.svg" alt="icon" width="24" height="24">
@ -147,75 +147,7 @@
</div> </div>
</li> </li>
<li class="nav-item"> <th:block th:insert="~{fragments/langAndDarkMode :: langAndDarkMode}"></th:block>
<a class="nav-link" id="dark-mode-toggle" href="#">
<img class="navbar-icon" id="dark-mode-icon" src="moon.svg" alt="icon" />
</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="languageDropdown" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-globe2" viewBox="0 0 20 20">
<path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm7.5-6.923c-.67.204-1.335.82-1.887 1.855-.143.268-.276.56-.395.872.705.157 1.472.257 2.282.287V1.077zM4.249 3.539c.142-.384.304-.744.481-1.078a6.7 6.7 0 0 1 .597-.933A7.01 7.01 0 0 0 3.051 3.05c.362.184.763.349 1.198.49zM3.509 7.5c.036-1.07.188-2.087.436-3.008a9.124 9.124 0 0 1-1.565-.667A6.964 6.964 0 0 0 1.018 7.5h2.49zm1.4-2.741a12.344 12.344 0 0 0-.4 2.741H7.5V5.091c-.91-.03-1.783-.145-2.591-.332zM8.5 5.09V7.5h2.99a12.342 12.342 0 0 0-.399-2.741c-.808.187-1.681.301-2.591.332zM4.51 8.5c.035.987.176 1.914.399 2.741A13.612 13.612 0 0 1 7.5 10.91V8.5H4.51zm3.99 0v2.409c.91.03 1.783.145 2.591.332.223-.827.364-1.754.4-2.741H8.5zm-3.282 3.696c.12.312.252.604.395.872.552 1.035 1.218 1.65 1.887 1.855V11.91c-.81.03-1.577.13-2.282.287zm.11 2.276a6.696 6.696 0 0 1-.598-.933 8.853 8.853 0 0 1-.481-1.079 8.38 8.38 0 0 0-1.198.49 7.01 7.01 0 0 0 2.276 1.522zm-1.383-2.964A13.36 13.36 0 0 1 3.508 8.5h-2.49a6.963 6.963 0 0 0 1.362 3.675c.47-.258.995-.482 1.565-.667zm6.728 2.964a7.009 7.009 0 0 0 2.275-1.521 8.376 8.376 0 0 0-1.197-.49 8.853 8.853 0 0 1-.481 1.078 6.688 6.688 0 0 1-.597.933zM8.5 11.909v3.014c.67-.204 1.335-.82 1.887-1.855.143-.268.276-.56.395-.872A12.63 12.63 0 0 0 8.5 11.91zm3.555-.401c.57.185 1.095.409 1.565.667A6.963 6.963 0 0 0 14.982 8.5h-2.49a13.36 13.36 0 0 1-.437 3.008zM14.982 7.5a6.963 6.963 0 0 0-1.362-3.675c-.47.258-.995.482-1.565.667.248.92.4 1.938.437 3.008h2.49zM11.27 2.461c.177.334.339.694.482 1.078a8.368 8.368 0 0 0 1.196-.49 7.01 7.01 0 0 0-2.275-1.52c.218.283.418.597.597.932zm-.488 1.343a7.765 7.765 0 0 0-.395-.872C9.835 1.897 9.17 1.282 8.5 1.077V4.09c.81-.03 1.577-.13 2.282-.287z"/>
</svg>
</a>
<div class="dropdown-menu" aria-labelledby="languageDropdown">
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="ar_AR">
<img src="images/flags/sa.svg" alt="icon" width="20" height="15"> العربية
</a>
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="ca_CA">
<img src="images/flags/es-ct.svg" alt="icon" width="20" height="15"> Català
</a>
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="zh_CN">
<img src="images/flags/cn.svg" alt="icon" width="20" height="15"> 简体中文
</a>
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="de_DE">
<img src="images/flags/de.svg" alt="icon" width="20" height="15"> Deutsch
</a>
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="en_GB">
<img src="images/flags/gb.svg" alt="icon" width="20" height="15"> English (GB)
</a>
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="en_US">
<img src="images/flags/us.svg" alt="icon" width="20" height="15"> English (US)
</a>
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="eu_ES">
<img src="images/flags/eu.svg" alt="icon" width="20" height="15"> Euskara
</a>
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="es_ES">
<img src="images/flags/es.svg" alt="icon" width="20" height="15"> Español
</a>
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="fr_FR">
<img src="images/flags/fr.svg" alt="icon" width="20" height="15"> Français
</a>
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="it_IT">
<img src="images/flags/it.svg" alt="icon" width="20" height="15"> Italiano
</a>
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="nl_NL">
<img src="images/flags/nl.svg" alt="icon" width="20" height="15"> Nederlands
</a>
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="pl_PL">
<img src="images/flags/pl.svg" alt="icon" width="20" height="15"> Polski
</a>
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="pt_BR">
<img src="images/flags/pt_br.svg" alt="icon" width="20" height="15"> Português (BR)
</a>
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="ro_RO">
<img src="images/flags/ro.svg" alt="icon" width="20" height="15"> Romanian
</a>
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="sv_SE">
<img src="images/flags/se.svg" alt="icon" width="20" height="15"> Svenska
</a>
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="ru_RU">
<img src="images/flags/ru.svg" alt="icon" width="20" height="15"> Русский
</a>
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="ko_KR">
<img src="images/flags/kr.svg" alt="icon" width="20" height="15"> 한국어
</a>
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="ja_JP">
<img src="images/flags/jp.svg" alt="icon" width="20" height="15"> 日本語
</a>
</div>
</li>
<li class="nav-item"> <li class="nav-item">

View file

@ -90,6 +90,7 @@
<div th:replace="~{fragments/card :: card(id='extract-page', cardTitle=#{home.extractPage.title}, cardText=#{home.extractPage.desc}, cardLink='extract-page', svgPath='images/extract.svg')}"></div> <div th:replace="~{fragments/card :: card(id='extract-page', cardTitle=#{home.extractPage.title}, cardText=#{home.extractPage.desc}, cardLink='extract-page', svgPath='images/extract.svg')}"></div>
<div th:replace="~{fragments/card :: card(id='pdf-to-single-page', cardTitle=#{home.PdfToSinglePage.title}, cardText=#{home.PdfToSinglePage.desc}, cardLink='pdf-to-single-page', svgPath='images/single-page.svg')}"></div> <div th:replace="~{fragments/card :: card(id='pdf-to-single-page', cardTitle=#{home.PdfToSinglePage.title}, cardText=#{home.PdfToSinglePage.desc}, cardLink='pdf-to-single-page', svgPath='images/single-page.svg')}"></div>
<div th:replace="~{fragments/card :: card(id='show-javascript', cardTitle=#{home.showJS.title}, cardText=#{home.showJS.desc}, cardLink='show-javascript', svgPath='images/js.svg')}"></div> <div th:replace="~{fragments/card :: card(id='show-javascript', cardTitle=#{home.showJS.title}, cardText=#{home.showJS.desc}, cardLink='show-javascript', svgPath='images/js.svg')}"></div>
<div th:replace="~{fragments/card :: card(id='auto-redact', cardTitle=#{home.autoRedact.title}, cardText=#{home.autoRedact.desc}, cardLink='auto-redact', svgPath='images/eraser-fill.svg')}"></div>

View file

@ -1,59 +1,99 @@
<html xmlns:th="http://www.thymeleaf.org"> <html xmlns:th="http://www.thymeleaf.org">
<!-- Hi if you have been redirected here when using API then you might need to supply a X-API-KEY key in header to authenticate! --> <th:block th:insert="~{fragments/common :: head(title=#{login.title})}"></th:block>
<head> <style>
<title>Login</title> html, body {
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"> height: 100%;
</head> }
body {
display: flex;
align-items: center;
padding-top: 40px;
padding-bottom: 40px;
background-color: #f5f5f5;
}
.form-signin {
width: 100%;
max-width: 330px;
padding: 15px;
margin: auto;
}
.form-signin .checkbox {
font-weight: 400;
}
.form-signin .form-floating:focus-within {
z-index: 2;
}
.form-signin input[type="text"] {
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.form-signin input[type="password"] {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.container-flex {
display: flex;
flex-direction: column;
min-height: 100vh;
width: 100%; /* Set width to 100% */
align-items: center; /* Center its children horizontally */
}
.footer-bottom {
margin-top: auto;
}
</style>
<body> <body>
<div class="container-flex">
<main class="form-signin text-center">
<div th:if="${logoutMessage}" class="alert alert-success"
th:text="${logoutMessage}"></div>
<div id="page-container">
<div id="content-wrap">
<div class="container">
<div class="row justify-content-center align-items-center" style="height:100vh;">
<div class="col-md-4">
<div class="login-container card">
<div class="card-header text-center">
<img src="favicon.svg" alt="Logo" class="img-fluid" style="max-width: 100px;"> <!-- Adjust path and style as needed -->
<h4>Stirling-PDF Login</h4>
</div>
<div th:if="${logoutMessage}" class="alert alert-success" th:text="${logoutMessage}"></div>
<div class="card-body">
<form th:action="@{/login}" method="post"> <form th:action="@{/login}" method="post">
<div class="mb-3"> <img class="mb-4" src="favicon.svg" alt="" width="144" height="144">
<label for="username">Username:</label> <h1 class="h1 mb-3 fw-normal">Stirling-PDF</h1>
<input type="text" id="username" name="username" class="form-control" required="required" /> <h2 class="h5 mb-3 fw-normal">Please sign in</h2>
<div class="form-floating">
<input type="text" class="form-control" id="username"
placeholder="admin"> <label for="username">Username</label>
</div> </div>
<div class="mb-3"> <div class="form-floating">
<label for="password">Password:</label> <input type="password" class="form-control" id="password"
<input type="password" id="password" name="password" class="form-control" required="required" /> placeholder="Password"> <label for="password">Password</label>
</div> </div>
<div class="mb-3 text-center">
<input type="submit" value="Login" class="btn btn-primary" /> <div class="checkbox mb-3">
<label> <input type="checkbox" value="remember-me">
Remember me
</label>
</div> </div>
<button class="w-100 btn btn-lg btn-primary" type="submit">Sign
in</button>
</form> </form>
</div> <div class="text-danger text-center">
<div class="card-footer text-danger text-center"> <div th:if="${error == 'badcredentials'}">Invalid username or
<div th:if="${error == 'badcredentials'}"> password.</div>
Invalid username or password. <div th:if="${error == 'locked'}">Your account has been locked.
</div>
<div th:if="${error == 'locked'}">
Your account has been locked.
</div>
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
</main>
<div th:insert="~{fragments/footer.html :: footer}"></div> <div th:insert="~{fragments/footer.html :: footer}"></div>
</div> </div>
</body> </body>
</html> </html>

View file

@ -2,8 +2,6 @@
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org"> <html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
<th:block th:insert="~{fragments/common :: head(title=#{autoRedact.title})}"></th:block> <th:block th:insert="~{fragments/common :: head(title=#{autoRedact.title})}"></th:block>
<body> <body>
<div id="page-container"> <div id="page-container">
<div id="content-wrap"> <div id="content-wrap">
@ -13,38 +11,37 @@
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6"> <div class="col-md-6">
<h2 th:text="#{autoRedact.header}"></h2> <h2 th:text="#{autoRedact.header}"></h2>
<form action="/auto-redact" method="post" enctype="multipart/form-data"> <form action="/auto-redact" method="post" enctype="multipart/form-data">
<div class="mb-3"> <div class="mb-3">
<input type="file" class="form-control" id="fileInput" name="fileInput" required accept="application/pdf"> <input type="file" class="form-control" id="fileInput" name="fileInput" required accept="application/pdf">
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="listOfText" class="form-label">Texts to Redact (line-separated)</label> <label for="listOfText" class="form-label" th:text="#{autoRedact.textsToRedactLabel}"></label>
<textarea class="form-control" id="listOfText" name="listOfText" rows="4" required placeholder="e.g. Confidential,Top-Secret"></textarea> <textarea class="form-control" id="listOfText" name="listOfText" rows="4" required th:placeholder="#{autoRedact.textsToRedactPlaceholder}"></textarea>
</div> </div>
<div class="mb-3 form-check"> <div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="useRegex" name="useRegex"> <input type="checkbox" class="form-check-input" id="useRegex" name="useRegex">
<label class="form-check-label" for="useRegex">Use Regex</label> <label class="form-check-label" for="useRegex" th:text="#{autoRedact.useRegexLabel}"></label>
</div> </div>
<div class="mb-3 form-check"> <div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="wholeWordSearch" name="wholeWordSearch"> <input type="checkbox" class="form-check-input" id="wholeWordSearch" name="wholeWordSearch">
<label class="form-check-label" for="wholeWordSearch">Whole Word Search</label> <label class="form-check-label" for="wholeWordSearch" th:text="#{autoRedact.wholeWordSearchLabel}"></label>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="customPadding" class="form-label">Custom Padding</label> <label for="customPadding" class="form-label" th:text="#{autoRedact.customPaddingLabel}"></label>
<input type="number" step="0.1" class="form-control" id="customPadding" name="customPadding" placeholder="0.0" value="0.1"> <input type="number" step="0.1" class="form-control" id="customPadding" name="customPadding" value="0.1">
</div> </div>
<div class="mb-3 form-check"> <div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="convertPDFToImage" name="convertPDFToImage" checked> <input type="checkbox" class="form-check-input" id="convertPDFToImage" name="convertPDFToImage" checked>
<label class="form-check-label" for="convertPDFToImage">Convert PDF to Image</label> <label class="form-check-label" for="convertPDFToImage" th:text="#{autoRedact.convertPDFToImageLabel}"></label>
</div> </div>
<button type="submit" class="btn btn-primary">Submit</button> <button type="submit" class="btn btn-primary" th:text="#{autoRedact.submitButton}"></button>
</form> </form>
</div> </div>
</div> </div>