@@ -205,30 +205,30 @@ def reconnect():
205205def update_thumbnails ():
206206 # Always allow manual thumbnail cache updates
207207 log .info ("Update of Cover cache requested" )
208-
208+
209209 try :
210210 from .tasks .thumbnail import TaskGenerateCoverThumbnails
211211 task_id = helper .update_thumbnail_cache ()
212-
212+
213213 # Check if there are any books to process
214214 books_with_covers = TaskGenerateCoverThumbnails .get_books_with_covers ()
215215 book_count = len (books_with_covers )
216-
216+
217217 if book_count > 0 :
218218 message = _ ('Thumbnail cache refresh started for {} book(s). This may take a few minutes.' ).format (book_count )
219219 else :
220220 message = _ ('No books with covers found to process.' )
221-
221+
222222 return jsonify ({
223- 'success' : True ,
223+ 'success' : True ,
224224 'message' : message ,
225225 'book_count' : book_count ,
226226 'task_id' : str (task_id ) if task_id else None
227227 })
228228 except Exception as e :
229229 log .error (f"Error starting thumbnail refresh: { e } " )
230230 return jsonify ({
231- 'success' : False ,
231+ 'success' : False ,
232232 'message' : _ ('Failed to start thumbnail refresh: {}' ).format (str (e ))
233233 })
234234
@@ -543,6 +543,8 @@ def edit_list_user(param):
543543 user .kobo_only_shelves_sync = int (vals ['value' ] == 'true' )
544544 elif param == 'kindle_mail' :
545545 user .kindle_mail = valid_email (vals ['value' ]) if vals ['value' ] else ""
546+ elif param == 'kindle_mail_subject' :
547+ user .kindle_mail_subject = vals ['value' ]
546548 elif param .endswith ('role' ):
547549 value = int (vals ['field_index' ])
548550 if user .name == "Guest" and value in \
@@ -1215,13 +1217,13 @@ def _configuration_oauth_helper(to_save):
12151217 if to_save ["config_generic_oauth_client_secret" ] != element ["oauth_client_secret" ]:
12161218 reboot_required = True
12171219 update ["oauth_client_secret" ] = to_save ["config_generic_oauth_client_secret" ]
1218-
1220+
12191221 # Handle metadata URL (takes precedence over manual configuration)
12201222 metadata_url = to_save .get ("config_generic_oauth_metadata_url" , "" )
12211223 if metadata_url != element .get ("metadata_url" , "" ):
12221224 reboot_required = True
12231225 update ["metadata_url" ] = metadata_url
1224-
1226+
12251227 # If metadata URL is provided, try to fetch endpoints
12261228 if metadata_url :
12271229 try :
@@ -1240,7 +1242,7 @@ def _configuration_oauth_helper(to_save):
12401242 log .warning (f"Failed to fetch OAuth metadata: { ex } " )
12411243 except Exception as ex :
12421244 log .error (f"Unexpected error fetching OAuth metadata: { ex } " )
1243-
1245+
12441246 # Handle manual server URL (fallback or override)
12451247 elif to_save ["config_generic_oauth_server_url" ] != element ["oauth_base_url" ]:
12461248 reboot_required = True
@@ -1264,41 +1266,41 @@ def _configuration_oauth_helper(to_save):
12641266 log .warning (f"Failed to fetch OIDC configuration: { ex } " )
12651267 except Exception as ex :
12661268 log .error (f"Unexpected error fetching OIDC configuration: { ex } " )
1267-
1269+
12681270 # Handle manual endpoint URLs if metadata URL is not used
12691271 if not metadata_url :
12701272 # Map form field names to database field names
12711273 endpoint_mappings = {
12721274 "config_generic_oauth_auth_url" : "oauth_authorize_url" ,
1273- "config_generic_oauth_token_url" : "oauth_token_url" ,
1275+ "config_generic_oauth_token_url" : "oauth_token_url" ,
12741276 "config_generic_oauth_userinfo_url" : "oauth_userinfo_url"
12751277 }
1276-
1278+
12771279 for form_field , db_field in endpoint_mappings .items ():
12781280 if form_field in to_save and to_save [form_field ] != element .get (db_field , "" ):
12791281 reboot_required = True
12801282 update [db_field ] = to_save [form_field ]
1281-
1283+
12821284 # Handle scope
12831285 if to_save .get ("config_generic_oauth_scope" , "" ) != element .get ("scope" , "" ):
12841286 reboot_required = True
12851287 update ["scope" ] = to_save .get ("config_generic_oauth_scope" , "" )
1286-
1288+
12871289 # Handle username mapper
12881290 if to_save .get ("config_generic_oauth_username_mapper" , "" ) != element .get ("username_mapper" , "" ):
12891291 reboot_required = True
12901292 update ["username_mapper" ] = to_save .get ("config_generic_oauth_username_mapper" , "" )
1291-
1293+
12921294 # Handle email mapper
12931295 if to_save .get ("config_generic_oauth_email_mapper" , "" ) != element .get ("email_mapper" , "" ):
12941296 reboot_required = True
12951297 update ["email_mapper" ] = to_save .get ("config_generic_oauth_email_mapper" , "" )
1296-
1298+
12971299 # Handle login button text
12981300 if to_save .get ("config_generic_oauth_login_button" , "" ) != element .get ("login_button" , "" ):
12991301 reboot_required = True
13001302 update ["login_button" ] = to_save .get ("config_generic_oauth_login_button" , "" )
1301-
1303+
13021304 if to_save ["config_generic_oauth_admin_group" ] != element ["oauth_admin_group" ]:
13031305 reboot_required = True
13041306 update ["oauth_admin_group" ] = to_save ["config_generic_oauth_admin_group" ]
@@ -1993,18 +1995,18 @@ def _configuration_update_helper():
19931995 _config_checkbox (to_save , "config_hardcover_sync" )
19941996 _config_checkbox (to_save , "config_hardcover_annotations_sync" )
19951997 _config_string (to_save , "config_hardcover_token" )
1996-
1998+
19971999 _config_int (to_save , "config_updatechannel" )
19982000
19992001 # Reverse proxy login configuration
20002002 _config_checkbox (to_save , "config_allow_reverse_proxy_header_login" )
20012003 _config_string (to_save , "config_reverse_proxy_login_header_name" )
20022004 _config_checkbox (to_save , "config_reverse_proxy_auto_create_users" )
2003-
2005+
20042006 # Validate reverse proxy configuration
20052007 if config .config_reverse_proxy_auto_create_users and not config .config_allow_reverse_proxy_header_login :
20062008 return _configuration_result (_ ('Auto-create users cannot be enabled without enabling reverse proxy authentication' ))
2007-
2009+
20082010 if config .config_reverse_proxy_auto_create_users and not config .config_reverse_proxy_login_header_name :
20092011 return _configuration_result (_ ('Auto-create users requires a valid reverse proxy header name' ))
20102012
@@ -2013,32 +2015,32 @@ def _configuration_update_helper():
20132015 if "config_oauth_redirect_host" in to_save :
20142016 old_host = getattr (config , 'config_oauth_redirect_host' , '' )
20152017 new_host = to_save ["config_oauth_redirect_host" ].strip ()
2016-
2018+
20172019 # Validate OAuth redirect host format if provided
20182020 if new_host :
20192021 try :
20202022 # Add https:// if no scheme is provided
20212023 if not new_host .startswith (('http://' , 'https://' )):
20222024 new_host = f"https://{ new_host } "
20232025 to_save ["config_oauth_redirect_host" ] = new_host
2024-
2026+
20252027 # Parse the URL to validate it
20262028 parsed = urlparse (new_host )
20272029 if not parsed .netloc :
20282030 return _configuration_result (_ ('Invalid OAuth Redirect Host format. Please include the full URL with protocol (e.g., https://your-domain.com)' ))
2029-
2031+
20302032 # Warn if URL contains a path (could cause redirect URI issues)
20312033 if parsed .path and parsed .path != '/' :
20322034 return _configuration_result (_ ('OAuth Redirect Host should not include a path. Use only the base URL (e.g., https://your-domain.com)' ))
2033-
2035+
20342036 except Exception :
20352037 return _configuration_result (_ ('Invalid OAuth Redirect Host format. Please include the full URL with protocol (e.g., https://your-domain.com)' ))
2036-
2038+
20372039 if old_host != new_host :
20382040 oauth_redirect_host_changed = True
2039-
2041+
20402042 _config_string (to_save , "config_oauth_redirect_host" )
2041-
2043+
20422044 if config .config_login_type == constants .LOGIN_OAUTH :
20432045 reboot , message = _configuration_oauth_helper (to_save )
20442046 if message :
@@ -2295,6 +2297,9 @@ def _handle_edit_user(to_save, content, languages, translations, kobo_support):
22952297 content .name = check_username (to_save ["name" ])
22962298 if to_save .get ("kindle_mail" ) != content .kindle_mail :
22972299 content .kindle_mail = valid_email (to_save ["kindle_mail" ]) if to_save ["kindle_mail" ] else ""
2300+ if to_save .get ("kindle_mail_subject" ) is not None :
2301+ content .kindle_mail_subject = (to_save .get ("kindle_mail_subject" , "" ) or "" ).strip ()
2302+
22982303 except Exception as ex :
22992304 log .error (ex )
23002305 flash (str (ex ), category = "error" )
@@ -2324,7 +2329,7 @@ def _handle_edit_user(to_save, content, languages, translations, kobo_support):
23242329
23252330
23262331def extract_user_data_from_field (user , field ):
2327- match = re .search (field + r"=(.*?)($|(?<!\\),)" , user , re .IGNORECASE | re .UNICODE )
2332+ match = re .search (field + r"=(.*?)($|(?<!\\),)" , user , re .IGNORECASE | re .UNICODE )
23282333 if match :
23292334 return match .group (1 )
23302335 else :
@@ -2362,7 +2367,7 @@ def test_oidc():
23622367 response .raise_for_status ()
23632368 # Try to parse the JSON and extract useful information
23642369 oidc_config = response .json ()
2365-
2370+
23662371 # Extract key endpoints for validation
23672372 endpoints = []
23682373 if 'authorization_endpoint' in oidc_config :
@@ -2371,12 +2376,12 @@ def test_oidc():
23712376 endpoints .append ('token' )
23722377 if 'userinfo_endpoint' in oidc_config :
23732378 endpoints .append ('userinfo' )
2374-
2379+
23752380 endpoint_info = " Found endpoints: " + ', ' .join (endpoints ) + "." if endpoints else ""
2376-
2381+
23772382 return json .dumps ({
2378- 'success' : True ,
2379- 'message' : _ ('Connection successful! OIDC discovery endpoint is accessible.%(endpoints)s' ,
2383+ 'success' : True ,
2384+ 'message' : _ ('Connection successful! OIDC discovery endpoint is accessible.%(endpoints)s' ,
23802385 endpoints = endpoint_info )
23812386 })
23822387 except requests .exceptions .HTTPError as e :
@@ -2410,31 +2415,31 @@ def test_metadata():
24102415 response = requests .get (metadata_url , timeout = 5 , verify = constants .OAUTH_SSL_STRICT )
24112416 response .raise_for_status ()
24122417 data = response .json ()
2413-
2418+
24142419 # Validate that it contains required OIDC fields
24152420 required_fields = ['issuer' , 'authorization_endpoint' , 'token_endpoint' ]
24162421 missing_fields = [field for field in required_fields if not data .get (field )]
2417-
2422+
24182423 if missing_fields :
24192424 return json .dumps ({
2420- 'success' : False ,
2421- 'message' : _ ('Metadata is missing required OIDC fields: %(fields)s. This may not be a valid OIDC metadata endpoint.' ,
2425+ 'success' : False ,
2426+ 'message' : _ ('Metadata is missing required OIDC fields: %(fields)s. This may not be a valid OIDC metadata endpoint.' ,
24222427 fields = ', ' .join (missing_fields ))
24232428 }), 200
2424-
2429+
24252430 # Count available OAuth endpoints for user feedback
2426- oauth_endpoints = ['authorization_endpoint' , 'token_endpoint' , 'userinfo_endpoint' ,
2431+ oauth_endpoints = ['authorization_endpoint' , 'token_endpoint' , 'userinfo_endpoint' ,
24272432 'end_session_endpoint' , 'introspection_endpoint' , 'revocation_endpoint' ]
24282433 found_endpoints = [ep for ep in oauth_endpoints if ep in data ]
24292434 endpoint_count = len (found_endpoints )
24302435 has_userinfo = 'userinfo_endpoint' in data
2431-
2436+
24322437 message = _ ('Metadata URL is valid! Found %(count)s OAuth endpoints.' , count = endpoint_count )
24332438 if has_userinfo :
24342439 message += _ (' User info endpoint is available.' )
24352440 else :
24362441 message += _ (' Note: User info endpoint not found - this may cause authentication issues.' )
2437-
2442+
24382443 return json .dumps ({'success' : True , 'message' : message })
24392444 except requests .exceptions .HTTPError as e :
24402445 if e .response .status_code == 404 :
0 commit comments