From 2f6ae855dd7e731e7e194bed0c6636c43537a20d Mon Sep 17 00:00:00 2001 From: Chris Debenham Date: Fri, 24 Oct 2025 14:57:08 +1100 Subject: [PATCH] Re-enable ForwardAuth and have ForwardAuth work using email address (#1426) * Fallback to using email for auth if user not defined by forwardauth * Re-enable RemoteAuth * Update build env --- .../controller/AuthenticationController.java | 13 +++++++++++++ .../model/dto/settings/PublicAppSetting.java | 1 + .../booklore/repository/UserRepository.java | 2 ++ .../service/appsettings/AppSettingService.java | 3 ++- .../src/app/core/security/auth-initializer.ts | 12 ++++++++++++ .../src/app/shared/service/app-settings.service.ts | 3 +++ booklore-ui/src/app/shared/service/auth.service.ts | 11 +++++++++++ dev.docker-compose.yml | 4 ++-- 8 files changed, 46 insertions(+), 3 deletions(-) diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/controller/AuthenticationController.java b/booklore-api/src/main/java/com/adityachandel/booklore/controller/AuthenticationController.java index d107af5cf..27ee9e80f 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/controller/AuthenticationController.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/controller/AuthenticationController.java @@ -6,6 +6,8 @@ import com.adityachandel.booklore.exception.ApiError; import com.adityachandel.booklore.model.dto.UserCreateRequest; import com.adityachandel.booklore.model.dto.request.RefreshTokenRequest; import com.adityachandel.booklore.model.dto.request.UserLoginRequest; +import com.adityachandel.booklore.model.entity.BookLoreUserEntity; +import com.adityachandel.booklore.repository.UserRepository; import com.adityachandel.booklore.service.user.UserProvisioningService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -21,6 +23,7 @@ import org.springframework.web.bind.annotation.*; import java.util.Locale; import java.util.Map; +import java.util.Optional; @Tag(name = "Authentication", description = "Endpoints for user authentication, registration, and token management") @Slf4j @@ -32,6 +35,7 @@ public class AuthenticationController { private final AppProperties appProperties; private final UserProvisioningService userProvisioningService; private final AuthenticationService authenticationService; + private final UserRepository userRepository; @Operation(summary = "Register a new user", description = "Register a new user. Only admins can register users.") @ApiResponses({ @@ -81,6 +85,15 @@ public class AuthenticationController { log.debug("Remote-Auth: retrieved values from headers: name: {}, username: {}, email: {}, groups: {}", name, username, email, groups); log.debug("Remote-Auth: remote auth settings: {}", appProperties.getRemoteAuth()); + if ((username == null || username.isEmpty()) && (email != null && !email.isEmpty())) { + log.debug("Remote-Auth: username is empty, trying to find user by email: {}", email); + Optional user = userRepository.findByEmail(email); + if (user.isPresent()) { + username = user.get().getUsername(); + log.debug("Remote-Auth: found user by email, username: {}", username); + } + } + return authenticationService.loginRemote(name, username, email, groups); } } diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/settings/PublicAppSetting.java b/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/settings/PublicAppSetting.java index b688da7ae..0e21a8bf5 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/settings/PublicAppSetting.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/settings/PublicAppSetting.java @@ -8,5 +8,6 @@ import lombok.*; @NoArgsConstructor public class PublicAppSetting { private boolean oidcEnabled; + private boolean remoteAuthEnabled; private OidcProviderDetails oidcProviderDetails; } \ No newline at end of file diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/repository/UserRepository.java b/booklore-api/src/main/java/com/adityachandel/booklore/repository/UserRepository.java index e4a4eb575..3558b1925 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/repository/UserRepository.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/repository/UserRepository.java @@ -12,6 +12,8 @@ public interface UserRepository extends JpaRepository Optional findByUsername(String username); + Optional findByEmail(String email); + Optional findById(Long id); List findAllByLibraries_Id(Long libraryId); diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/service/appsettings/AppSettingService.java b/booklore-api/src/main/java/com/adityachandel/booklore/service/appsettings/AppSettingService.java index 91cb89710..46628f103 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/service/appsettings/AppSettingService.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/service/appsettings/AppSettingService.java @@ -73,6 +73,7 @@ public class AppSettingService { PublicAppSetting.PublicAppSettingBuilder builder = PublicAppSetting.builder(); builder.oidcEnabled(Boolean.parseBoolean(settingPersistenceHelper.getOrCreateSetting(AppSettingKey.OIDC_ENABLED, "false"))); + builder.remoteAuthEnabled(appProperties.getRemoteAuth().isEnabled()); builder.oidcProviderDetails(settingPersistenceHelper.getJsonSetting(settingsMap, AppSettingKey.OIDC_PROVIDER_DETAILS, OidcProviderDetails.class, null, false)); return builder.build(); @@ -111,4 +112,4 @@ public class AppSettingService { return builder.build(); } -} \ No newline at end of file +} diff --git a/booklore-ui/src/app/core/security/auth-initializer.ts b/booklore-ui/src/app/core/security/auth-initializer.ts index ea81ec06f..99f7da75d 100644 --- a/booklore-ui/src/app/core/security/auth-initializer.ts +++ b/booklore-ui/src/app/core/security/auth-initializer.ts @@ -94,6 +94,18 @@ export function initializeAuthFactory() { resolve(); }); + } else if (publicSettings.remoteAuthEnabled) { + authService.remoteLogin().subscribe({ + next: () => { + authInitService.markAsInitialized(); + resolve(); + }, + error: err => { + console.error('[Remote Login] failed:', err); + authInitService.markAsInitialized(); + resolve(); + } + }); } else { if (forceLocalOnly) { console.warn('[OIDC] Forced local-only login via ?localOnly=true'); diff --git a/booklore-ui/src/app/shared/service/app-settings.service.ts b/booklore-ui/src/app/shared/service/app-settings.service.ts index 3c2212241..50ed575fa 100644 --- a/booklore-ui/src/app/shared/service/app-settings.service.ts +++ b/booklore-ui/src/app/shared/service/app-settings.service.ts @@ -8,6 +8,7 @@ import {AuthService} from './auth.service'; export interface PublicAppSettings { oidcEnabled: boolean; + remoteAuthEnabled: boolean; oidcProviderDetails: OidcProviderDetails; } @@ -76,6 +77,7 @@ export class AppSettingsService { private syncPublicSettings(appSettings: AppSettings): void { const updatedPublicSettings: PublicAppSettings = { oidcEnabled: appSettings.oidcEnabled, + remoteAuthEnabled: appSettings.remoteAuthEnabled, oidcProviderDetails: appSettings.oidcProviderDetails }; const current = this.publicAppSettingsSubject.value; @@ -83,6 +85,7 @@ export class AppSettingsService { if ( !current || current.oidcEnabled !== updatedPublicSettings.oidcEnabled || + current.remoteAuthEnabled !== updatedPublicSettings.remoteAuthEnabled || JSON.stringify(current.oidcProviderDetails) !== JSON.stringify(updatedPublicSettings.oidcProviderDetails) ) { this.publicAppSettingsSubject.next(updatedPublicSettings); diff --git a/booklore-ui/src/app/shared/service/auth.service.ts b/booklore-ui/src/app/shared/service/auth.service.ts index 55f203e55..68315d1d2 100644 --- a/booklore-ui/src/app/shared/service/auth.service.ts +++ b/booklore-ui/src/app/shared/service/auth.service.ts @@ -47,6 +47,17 @@ export class AuthService { ); } + remoteLogin(): Observable<{ accessToken: string; refreshToken: string, isDefaultPassword: string }> { + return this.http.get<{ accessToken: string; refreshToken: string, isDefaultPassword: string }>(`${this.apiUrl}/remote`).pipe( + tap((response) => { + if (response.accessToken && response.refreshToken) { + this.saveInternalTokens(response.accessToken, response.refreshToken); + this.initializeWebSocketConnection(); + } + }) + ); + } + saveInternalTokens(accessToken: string, refreshToken: string): void { localStorage.setItem('accessToken_Internal', accessToken); localStorage.setItem('refreshToken_Internal', refreshToken); diff --git a/dev.docker-compose.yml b/dev.docker-compose.yml index 4ac12b568..631c49b9d 100644 --- a/dev.docker-compose.yml +++ b/dev.docker-compose.yml @@ -1,6 +1,6 @@ services: backend: - image: gradle:8-jdk21-alpine + image: gradle:9-jdk25-alpine command: sh -c "cd /booklore-api && ./gradlew bootRun" ports: - "${BACKEND_PORT:-8080}:8080" @@ -58,4 +58,4 @@ services: volumes: node_modules: - backend_db: \ No newline at end of file + backend_db: