From 7d006355c7d7a9bd48c6845a83ea226cf7734389 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Louis-Andr=C3=A9=20Labadie?= Date: Wed, 6 Aug 2025 13:30:46 -0400 Subject: [PATCH] Feat: ForwardAuth user creation follows OIDC library attribution preferences (#805) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ForwardAuth user provisioning: Assign default permissions when available * Add forward auth mention in README + details in a separate doc Add Forward Auth docs * Fix: PermissionDeleteBooks → PermissionDeleteBook --------- Co-authored-by: Aditya Chandel <8075870+adityachandelgit@users.noreply.github.com> --- README.md | 11 ++- .../service/user/UserProvisioningService.java | 33 +++++++-- docs/forward-auth-with-proxy.md | 68 +++++++++++++++++++ 3 files changed, 103 insertions(+), 9 deletions(-) create mode 100644 docs/forward-auth-with-proxy.md diff --git a/README.md b/README.md index 822f0f701..c3069e158 100644 --- a/README.md +++ b/README.md @@ -159,8 +159,8 @@ services: - /your/local/path/to/booklore/data:/app/data - /your/local/path/to/booklore/books:/books - /your/local/path/to/booklore/bookdrop:/bookdrop # 👈 Bookdrop directory -``` - +``` + ## 🔑 OIDC/OAuth2 Authentication (Authentik, Pocket ID, etc.) @@ -173,6 +173,13 @@ For detailed instructions on setting up OIDC authentication: - 📺 [YouTube video on configuring Authentik with BookLore](https://www.youtube.com/watch?v=r6Ufh9ldF9M) - 📘 [Step-by-step setup guide for Pocket ID](docs/OIDC-Setup-With-PocketID.md) +## 🛡️ Forward Auth with Reverse Proxy + +BookLore also supports **Forward Auth** (also known as Remote Auth) for authentication through reverse proxies like **Traefik**, **Nginx**, or **Caddy**. Forward Auth works by having your reverse proxy handle authentication and pass user information via HTTP headers to BookLore. This can be set up with providers like **Authelia** and **Authentik**. + +For detailed setup instructions and configuration examples: +- 📘 [Complete Forward Auth Setup Guide](docs/forward-auth-with-proxy.md) + ## 🤝 Community & Support - 🐞 Found a bug? [Open an issue](https://github.com/adityachandelgit/BookLore/issues) diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/service/user/UserProvisioningService.java b/booklore-api/src/main/java/com/adityachandel/booklore/service/user/UserProvisioningService.java index a703f108a..1604c0e78 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/service/user/UserProvisioningService.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/service/user/UserProvisioningService.java @@ -9,6 +9,7 @@ import com.adityachandel.booklore.model.entity.*; import com.adityachandel.booklore.model.enums.*; import com.adityachandel.booklore.repository.LibraryRepository; import com.adityachandel.booklore.repository.UserRepository; +import com.adityachandel.booklore.service.appsettings.AppSettingService; import jakarta.transaction.Transactional; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -28,6 +29,7 @@ public class UserProvisioningService { private final LibraryRepository libraryRepository; private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); private final UserDefaultsService userDefaultsService; + private final AppSettingService appSettingService; public boolean isInitialUserAlreadyProvisioned() { return userRepository.count() > 0; @@ -107,7 +109,7 @@ public class UserProvisioningService { perms.setPermissionEditMetadata(defaultPermissions.contains("permissionEditMetadata")); perms.setPermissionManipulateLibrary(defaultPermissions.contains("permissionManipulateLibrary")); perms.setPermissionEmailBook(defaultPermissions.contains("permissionEmailBook")); - perms.setPermissionDeleteBook(defaultPermissions.contains("permissionDeleteBooks")); + perms.setPermissionDeleteBook(defaultPermissions.contains("permissionDeleteBook")); } user.setPermissions(perms); @@ -142,19 +144,37 @@ public class UserProvisioningService { user.setProvisioningMethod(ProvisioningMethod.REMOTE); user.setPasswordHash("RemoteUser_" + RandomStringUtils.secure().nextAlphanumeric(32)); + OidcAutoProvisionDetails oidcAutoProvisionDetails = appSettingService.getAppSettings().getOidcAutoProvisionDetails(); + UserPermissionsEntity permissions = new UserPermissionsEntity(); permissions.setUser(user); - permissions.setPermissionUpload(true); - permissions.setPermissionDownload(true); - permissions.setPermissionEditMetadata(true); - permissions.setPermissionEmailBook(true); - permissions.setPermissionDeleteBook(true); + + if (oidcAutoProvisionDetails != null && oidcAutoProvisionDetails.getDefaultPermissions() != null) { + List defaultPermissions = oidcAutoProvisionDetails.getDefaultPermissions(); + permissions.setPermissionUpload(defaultPermissions.contains("permissionUpload")); + permissions.setPermissionDownload(defaultPermissions.contains("permissionDownload")); + permissions.setPermissionEditMetadata(defaultPermissions.contains("permissionEditMetadata")); + permissions.setPermissionManipulateLibrary(defaultPermissions.contains("permissionManipulateLibrary")); + permissions.setPermissionEmailBook(defaultPermissions.contains("permissionEmailBook")); + permissions.setPermissionDeleteBook(defaultPermissions.contains("permissionDeleteBook")); + } else { + permissions.setPermissionUpload(true); + permissions.setPermissionDownload(true); + permissions.setPermissionEditMetadata(true); + permissions.setPermissionManipulateLibrary(false); + permissions.setPermissionEmailBook(true); + permissions.setPermissionDeleteBook(true); + } + permissions.setPermissionAdmin(isAdmin); user.setPermissions(permissions); if (isAdmin) { List libraries = libraryRepository.findAll(); user.setLibraries(new ArrayList<>(libraries)); + } else if (oidcAutoProvisionDetails != null && oidcAutoProvisionDetails.getDefaultLibraryIds() != null && !oidcAutoProvisionDetails.getDefaultLibraryIds().isEmpty()) { + List libraries = libraryRepository.findAllById(oidcAutoProvisionDetails.getDefaultLibraryIds()); + user.setLibraries(new ArrayList<>(libraries)); } return createUser(user); @@ -166,4 +186,3 @@ public class UserProvisioningService { userDefaultsService.addDefaultSettings(user); return user; } -} \ No newline at end of file diff --git a/docs/forward-auth-with-proxy.md b/docs/forward-auth-with-proxy.md new file mode 100644 index 000000000..f0914afee --- /dev/null +++ b/docs/forward-auth-with-proxy.md @@ -0,0 +1,68 @@ +# Forward Auth with Reverse Proxy + +BookLore supports **Forward Auth**, allowing you to specify when a user is logged in using a reverse proxy and existing SSO provider. + +## ⚠️ Security + +** Important**: Enabling forward auth means BookLore will **fully trust headers sent by the reverse proxy**. Never expose BookLore directly to the internet when using forward auth - always route through your authenticated proxy, otherwise outsiders can attempt to impersonate any username they know about. + +## Configuration + +Provide BookLore with the following environment variables: + +```bash +# Allows Forward Auth +REMOTE_AUTH_ENABLED=true + +# Enable automatic user creation (recommended) +REMOTE_AUTH_CREATE_NEW_USERS=true + +# Header names (your proxy will specify what header names to use) +REMOTE_AUTH_HEADER_USER=Remote-User # Username (required) +REMOTE_AUTH_HEADER_NAME=Remote-Name # Display name +REMOTE_AUTH_HEADER_EMAIL=Remote-Email # Email address +REMOTE_AUTH_HEADER_GROUPS=Remote-Groups # Groups/roles + +# Admin group name (optional) +REMOTE_AUTH_ADMIN_GROUP=admin # Specify this if you want a group to automatically get admin rights +``` + +### Docker Compose Example + +```yaml +services: + booklore: + image: ghcr.io/adityachandelgit/booklore-app:latest + environment: + # Forward Auth Configuration + - REMOTE_AUTH_ENABLED=true + - REMOTE_AUTH_CREATE_NEW_USERS=true + - REMOTE_AUTH_HEADER_NAME=Remote-Name + - REMOTE_AUTH_HEADER_USER=Remote-User + - REMOTE_AUTH_HEADER_EMAIL=Remote-Email + - REMOTE_AUTH_HEADER_GROUPS=Remote-Groups + - REMOTE_AUTH_ADMIN_GROUP=admin + # ... rest of configuration ... +``` + +## Setting Up Defaults Permissions + +1. **Access Admin Settings**: Log in to Booklore as an admin user +2. **Navigate to Authentication Settings**: Go to Settings → Authentication +3. **Configure OIDC Auto-Provision** (even if not using OIDC): + - Enable "Auto User Provisioning". You might need to enter a bogus URL to enable it temporarily. + - Select the default permissions and libraries for new users. +4. **Save Settings** + +## Example: Caddyfile for Authelia Forward Auth + +```caddyfile +books.example.com { + forward_auth authelia:9091 { + uri /api/authz/forward-auth + copy_headers Remote-User Remote-Name Remote-Email Remote-Groups + } + + reverse_proxy booklore:6060 +} +```