This commit is contained in:
aditya.chandel committed 2025-11-30 20:05:49 -07:00
1 parent 132e3e3f92
commit 8487d3ed75
9 files changed
+64 -53

No files matched your search

+4 -4
View File
@@ -21,6 +21,8 @@ WORKDIR /springboot-app
COPY ./booklore-api/build.gradle ./booklore-api/settings.gradle /springboot-app/
COPY ./booklore-api/src /springboot-app/src
COPY --from=angular-build /angular-app/dist/booklore/browser /springboot-app/src/main/resources/static
# Inject version into application.yaml using yq
ARG APP_VERSION
RUN apk add --no-cache yq && \
@@ -45,14 +47,12 @@ LABEL org.opencontainers.image.title="BookLore" \
org.opencontainers.image.licenses="GPL-3.0" \
org.opencontainers.image.base.name="docker.io/library/eclipse-temurin:21.0.9_10-jre-alpine"
RUN apk update && apk add nginx gettext su-exec
RUN apk update && apk add su-exec
COPY ./nginx.conf /etc/nginx/nginx.conf
COPY --from=angular-build /angular-app/dist/booklore/browser /usr/share/nginx/html
COPY --from=springboot-build /springboot-app/build/libs/booklore-api-0.0.1-SNAPSHOT.jar /app/app.jar
COPY start.sh /start.sh
RUN chmod +x /start.sh
EXPOSE 8080 80
EXPOSE 8080
CMD ["/start.sh"]
+3 -5
View File
@@ -84,10 +84,9 @@ ### 2️⃣ Set Up Your docker-compose.yml Configuration
```ini
# BookLore Application Settings
APP_USER_ID=0
APP_GROUP_ID=0
APP_USER_ID=1000
APP_GROUP_ID=1000
TZ=Etc/UTC
BOOKLORE_PORT=6060
# Database Connection (BookLore)
DATABASE_URL=jdbc:mariadb://mariadb:3306/booklore
@@ -117,12 +116,11 @@ # MariaDB Container Settings
- DATABASE_URL=${DATABASE_URL}
- DATABASE_USERNAME=${DB_USER}
- DATABASE_PASSWORD=${DB_PASSWORD}
- BOOKLORE_PORT=${BOOKLORE_PORT}
depends_on:
mariadb:
condition: service_healthy
ports:
- "${BOOKLORE_PORT}:${BOOKLORE_PORT}"
- "8080:8080"
volumes:
- ./data:/app/data
- ./books:/books
@@ -3,8 +3,14 @@ package com.adityachandel.booklore.config;
import com.adityachandel.booklore.interceptor.OpdsEnabledInterceptor;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.resource.PathResourceResolver;
import java.io.IOException;
@Configuration
@RequiredArgsConstructor
@@ -17,4 +23,21 @@ public class WebMvcConfig implements WebMvcConfigurer {
registry.addInterceptor(opdsEnabledInterceptor)
.addPathPatterns("/api/v1/opds/**", "/api/v2/opds/**");
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/static")
.resourceChain(true)
.addResolver(new PathResourceResolver() {
@Override
protected Resource getResource(String resourcePath, Resource location) throws IOException {
Resource requestedResource = location.createRelative(resourcePath);
if (requestedResource.exists() && requestedResource.isReadable()) {
return requestedResource;
}
return new ClassPathResource("/static/index.html");
}
});
}
}
@@ -5,7 +5,13 @@ import com.adityachandel.booklore.config.security.filter.CoverJwtFilter;
import com.adityachandel.booklore.config.security.filter.DualJwtAuthenticationFilter;
import com.adityachandel.booklore.config.security.filter.KoboAuthFilter;
import com.adityachandel.booklore.config.security.filter.KoreaderAuthFilter;
import com.adityachandel.booklore.config.security.service.DynamicOidcJwtProcessor;
import com.adityachandel.booklore.config.security.service.OpdsUserDetailsService;
import com.adityachandel.booklore.mapper.custom.BookLoreUserTransformer;
import com.adityachandel.booklore.repository.KoboUserSettingsRepository;
import com.adityachandel.booklore.repository.KoreaderUserRepository;
import com.adityachandel.booklore.repository.UserRepository;
import com.adityachandel.booklore.service.appsettings.AppSettingService;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -39,6 +45,13 @@ public class SecurityConfig {
private final OpdsUserDetailsService opdsUserDetailsService;
private final DualJwtAuthenticationFilter dualJwtAuthenticationFilter;
private final AppProperties appProperties;
private final KoboUserSettingsRepository koboUserSettingsRepository;
private final UserRepository userRepository;
private final BookLoreUserTransformer bookLoreUserTransformer;
private final KoreaderUserRepository koreaderUserRepository;
private final JwtUtils jwtUtils;
private final AppSettingService appSettingService;
private final DynamicOidcJwtProcessor dynamicOidcJwtProcessor;
private static final String[] SWAGGER_ENDPOINTS = {
"/api/v1/swagger-ui.html",
@@ -90,31 +103,31 @@ public class SecurityConfig {
@Bean
@Order(2)
public SecurityFilterChain koreaderSecurityChain(HttpSecurity http, KoreaderAuthFilter koreaderAuthFilter) throws Exception {
public SecurityFilterChain koreaderSecurityChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/api/koreader/**")
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.addFilterBefore(koreaderAuthFilter, UsernamePasswordAuthenticationFilter.class);
.addFilterBefore(new KoreaderAuthFilter(koreaderUserRepository), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
@Order(3)
public SecurityFilterChain koboSecurityChain(HttpSecurity http, KoboAuthFilter koboAuthFilter) throws Exception {
public SecurityFilterChain koboSecurityChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/api/kobo/**")
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.addFilterBefore(koboAuthFilter, UsernamePasswordAuthenticationFilter.class);
.addFilterBefore(new KoboAuthFilter(koboUserSettingsRepository, userRepository, bookLoreUserTransformer), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
@Order(4)
public SecurityFilterChain coverJwtApiSecurityChain(HttpSecurity http, CoverJwtFilter coverJwtFilter) throws Exception {
public SecurityFilterChain coverJwtApiSecurityChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/api/v1/media/**")
.csrf(AbstractHttpConfigurer::disable)
@@ -125,7 +138,7 @@ public class SecurityConfig {
.authorizeHttpRequests(auth -> auth
.anyRequest().permitAll()
)
.addFilterBefore(coverJwtFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new CoverJwtFilter(jwtUtils, userRepository, bookLoreUserTransformer, appSettingService, dynamicOidcJwtProcessor), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(dualJwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
@@ -133,6 +146,19 @@ public class SecurityConfig {
@Bean
@Order(5)
public SecurityFilterChain staticResourcesSecurityChain(HttpSecurity http) throws Exception {
http
.securityMatcher(request -> {
String path = request.getRequestURI();
return !path.startsWith("/api/");
})
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth.anyRequest().permitAll());
return http.build();
}
@Bean
@Order(6)
public SecurityFilterChain jwtApiSecurityChain(HttpSecurity http) throws Exception {
List<String> publicEndpoints = new ArrayList<>(Arrays.asList(COMMON_PUBLIC_ENDPOINTS));
if (appProperties.getSwagger().isEnabled()) {
@@ -152,7 +178,6 @@ public class SecurityConfig {
@Bean
public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
// Configure the shared AuthenticationManagerBuilder with the UserDetailsService and PasswordEncoder
AuthenticationManagerBuilder auth = http.getSharedObject(AuthenticationManagerBuilder.class);
auth.userDetailsService(opdsUserDetailsService).passwordEncoder(passwordEncoder());
return auth.build();
@@ -17,14 +17,12 @@ import lombok.AllArgsConstructor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.time.Instant;
@AllArgsConstructor
@Component
public class CoverJwtFilter extends OncePerRequestFilter {
private final JwtUtils jwtUtils;
@@ -33,12 +31,6 @@ public class CoverJwtFilter extends OncePerRequestFilter {
private final AppSettingService appSettingService;
private final DynamicOidcJwtProcessor dynamicOidcJwtProcessor;
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
String path = request.getRequestURI();
return !path.startsWith("/api/v1/media/");
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
String token = request.getParameter("token");
@@ -44,23 +44,13 @@ public class DualJwtAuthenticationFilter extends OncePerRequestFilter {
private static final ConcurrentMap<String, Object> userLocks = new ConcurrentHashMap<>();
private final DynamicOidcJwtProcessor dynamicOidcJwtProcessor;
private static final List<String> WHITELISTED_PATHS = List.of(
"/api/v1/opds/",
"/api/v2/opds/",
"/api/v1/auth/refresh",
"/api/v1/setup/",
"/api/kobo/"
);
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String token = extractToken(request);
String path = request.getRequestURI();
boolean isWhitelisted = WHITELISTED_PATHS.stream().anyMatch(path::startsWith);
if (isWhitelisted) {
if (!path.startsWith("/api/")) {
chain.doFilter(request, response);
return;
}
@@ -16,7 +16,6 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@@ -25,7 +24,6 @@ import java.util.List;
@Slf4j
@RequiredArgsConstructor
@Component
public class KoboAuthFilter extends OncePerRequestFilter {
private final KoboUserSettingsRepository koboUserSettingsRepository;
@@ -34,14 +32,8 @@ public class KoboAuthFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String path = request.getRequestURI();
if (!path.startsWith("/api/kobo/")) {
filterChain.doFilter(request, response);
return;
}
String[] parts = path.split("/");
if (parts.length < 4) {
log.warn("KOBO token missing in path");
@@ -12,14 +12,12 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.util.List;
@RequiredArgsConstructor
@Component
@Slf4j
public class KoreaderAuthFilter extends OncePerRequestFilter {
@@ -27,13 +25,6 @@ public class KoreaderAuthFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
String path = request.getRequestURI();
if (!path.startsWith("/api/koreader/")) {
chain.doFilter(request, response);
return;
}
String username = request.getHeader("x-auth-user");
String key = request.getHeader("x-auth-key");
@@ -16,7 +16,7 @@ app:
server:
forward-headers-strategy: native
port: 8080
port: ${BOOKLORE_PORT:8080}
tomcat:
relaxed-query-chars: '[,],%,{,},|'