mirror of
https://github.com/booklore-app/booklore.git
synced 2026-08-07 07:52:05 -04:00
WIP
This commit is contained in:
1 parent
132e3e3f92
commit
8487d3ed75
9 files changed
+64
-53
No files matched your search
+4
-4
@@ -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"]
|
||||
@@ -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");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
+32
-7
@@ -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();
|
||||
|
||||
-8
@@ -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");
|
||||
|
||||
+1
-11
@@ -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;
|
||||
}
|
||||
|
||||
-8
@@ -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");
|
||||
|
||||
-9
@@ -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: '[,],%,{,},|'
|
||||
|
||||
|
||||
Reference in new issue
Block a user