diff --git a/.github/instructions/update_release_notes.instructions.md b/.github/instructions/update_release_notes.instructions.md new file mode 100644 index 00000000..353cea48 --- /dev/null +++ b/.github/instructions/update_release_notes.instructions.md @@ -0,0 +1,90 @@ +--- +applyTo: '**' +--- + +# Release Notes Update Instructions + +## When to Update Release Notes + +After completing a code change (bug fix, new feature, enhancement, or breaking change), always ask the user: + +**"Would you like me to update the release notes in `docs/explanation/release_notes.md`?"** + +## If the User Confirms Yes + +Update the release notes file following these guidelines: + +### 1. Location +Release notes are located at: `docs/explanation/release_notes.md` + +### 2. Version Placement +- Add new entries under the **current version** from `config.py` +- If the version has changed, create a new version section at the TOP of the file +- Format: `### **(vX.XXX.XXX)**` + +### 3. Entry Categories + +Organize entries under the appropriate category: + +#### New Features +```markdown +#### New Features + +* **Feature Name** + * Brief description of what the feature does and its benefits. + * Additional details about functionality or configuration. + * (Ref: relevant files, components, or concepts) +``` + +#### Bug Fixes +```markdown +#### Bug Fixes + +* **Fix Name** + * Description of what was broken and how it was fixed. + * Impact or affected areas. + * (Ref: relevant files, functions, or components) +``` + +#### User Interface Enhancements +```markdown +#### User Interface Enhancements + +* **Enhancement Name** + * Description of UI/UX improvements. + * (Ref: relevant templates, CSS, or JavaScript files) +``` + +#### Breaking Changes +```markdown +#### Breaking Changes + +* **Change Name** + * Description of what changed and why. + * **Migration**: Steps users need to take (if any). +``` + +### 4. Entry Format Guidelines + +- **Bold the title** of each entry +- Use bullet points for details +- Include a `(Ref: ...)` line with relevant file names, functions, or concepts +- Keep descriptions concise but informative +- Focus on user-facing impact, not implementation details + +### 5. Example Entry + +```markdown +* **Custom Logo Display Fix** + * Fixed issue where custom logos uploaded via Admin Settings would only display on the admin page but not on other pages (chat, sidebar, landing page). + * Root cause was overly aggressive sanitization removing logo URLs from public settings. + * (Ref: logo display, settings sanitization, template conditionals) +``` + +### 6. Checklist Before Updating + +- [ ] Confirm the current version in `config.py` +- [ ] Determine the correct category (New Feature, Bug Fix, Enhancement, Breaking Change) +- [ ] Write a clear, user-focused description +- [ ] Include relevant file/component references +- [ ] Place entry under the correct version section diff --git a/application/single_app/config.py b/application/single_app/config.py index 12906ce8..9a5c892f 100644 --- a/application/single_app/config.py +++ b/application/single_app/config.py @@ -88,7 +88,7 @@ EXECUTOR_TYPE = 'thread' EXECUTOR_MAX_WORKERS = 30 SESSION_TYPE = 'filesystem' -VERSION = "0.237.001" +VERSION = "0.237.003" SECRET_KEY = os.getenv('SECRET_KEY', 'dev-secret-key-change-in-production') diff --git a/application/single_app/functions_settings.py b/application/single_app/functions_settings.py index 7a411064..5fa59f12 100644 --- a/application/single_app/functions_settings.py +++ b/application/single_app/functions_settings.py @@ -794,6 +794,15 @@ def sanitize_settings_for_user(full_settings: dict) -> dict: else: sanitized[k] = v + # Add boolean flags for logo/favicon existence so templates can check without exposing base64 data + # These fields are stripped by the base64 filter above, but templates need to know if logos exist + if 'custom_logo_base64' in full_settings: + sanitized['custom_logo_base64'] = bool(full_settings.get('custom_logo_base64')) + if 'custom_logo_dark_base64' in full_settings: + sanitized['custom_logo_dark_base64'] = bool(full_settings.get('custom_logo_dark_base64')) + if 'custom_favicon_base64' in full_settings: + sanitized['custom_favicon_base64'] = bool(full_settings.get('custom_favicon_base64')) + return sanitized def sanitize_settings_for_logging(full_settings: dict) -> dict: diff --git a/application/single_app/static/images/custom_logo.png b/application/single_app/static/images/custom_logo.png index 45a99fd3..ecf6e652 100644 Binary files a/application/single_app/static/images/custom_logo.png and b/application/single_app/static/images/custom_logo.png differ diff --git a/application/single_app/static/images/custom_logo_dark.png b/application/single_app/static/images/custom_logo_dark.png index b3beb694..4f281945 100644 Binary files a/application/single_app/static/images/custom_logo_dark.png and b/application/single_app/static/images/custom_logo_dark.png differ diff --git a/application/single_app/static/images/favicon.ico b/application/single_app/static/images/favicon.ico index d8f058f6..3dc7742a 100644 Binary files a/application/single_app/static/images/favicon.ico and b/application/single_app/static/images/favicon.ico differ diff --git a/application/single_app/templates/admin_settings.html b/application/single_app/templates/admin_settings.html index 7ef20f2d..70edcc45 100644 --- a/application/single_app/templates/admin_settings.html +++ b/application/single_app/templates/admin_settings.html @@ -2463,8 +2463,14 @@
Default Retention Policies + + + + + + @@ -2479,8 +2485,14 @@
Default Retention Policies + + + + + + @@ -2502,8 +2514,14 @@
Default Retention Policies + + + + + + @@ -2518,8 +2536,14 @@
Default Retention Policies + + + + + + @@ -2541,8 +2565,14 @@
Default Retention Policies + + + + + + @@ -2557,8 +2587,14 @@
Default Retention Policies + + + + + + diff --git a/application/single_app/templates/control_center.html b/application/single_app/templates/control_center.html index 853b4631..7a86d961 100644 --- a/application/single_app/templates/control_center.html +++ b/application/single_app/templates/control_center.html @@ -1670,8 +1670,14 @@
Retention PolicyUsing organization default + + + + + + @@ -1687,8 +1693,14 @@
Retention PolicyUsing organization default + + + + + + @@ -2287,8 +2299,14 @@
Retention PolicyUsing organization default + + + + + + @@ -2304,8 +2322,14 @@
Retention PolicyUsing organization default + + + + + + diff --git a/application/single_app/templates/profile.html b/application/single_app/templates/profile.html index e5a62887..2ab543f7 100644 --- a/application/single_app/templates/profile.html +++ b/application/single_app/templates/profile.html @@ -319,8 +319,14 @@
Retention Policy Sett + + + + + + @@ -338,8 +344,14 @@
Retention Policy Sett + + + + + + diff --git a/docs/explanation/fixes/v0.237.003/CUSTOM_LOGO_NOT_DISPLAYING_FIX.md b/docs/explanation/fixes/v0.237.003/CUSTOM_LOGO_NOT_DISPLAYING_FIX.md new file mode 100644 index 00000000..166dc7c9 --- /dev/null +++ b/docs/explanation/fixes/v0.237.003/CUSTOM_LOGO_NOT_DISPLAYING_FIX.md @@ -0,0 +1,102 @@ +# Custom Logo Not Displaying Across App Fix + +## Issue Description +When an admin uploaded a custom logo via Admin Settings, the logo would display correctly on the admin settings page but **not appear elsewhere in the application** (e.g., chat page, sidebar navigation). + +### Symptoms +- Logo visible in Admin Settings preview +- Logo not appearing in sidebar navigation +- Logo not appearing on chat/chats pages +- Logo not appearing on index/landing page + +## Root Cause Analysis +The issue was in the `sanitize_settings_for_user()` function in [functions_settings.py](../../application/single_app/functions_settings.py). + +This function is designed to strip sensitive data before sending settings to the frontend. It filters out any keys containing terms like: +- `key` +- `secret` +- `password` +- `connection` +- **`base64`** +- `storage_account_url` + +The logo settings are stored with keys: +- `custom_logo_base64` +- `custom_logo_dark_base64` +- `custom_favicon_base64` + +Because these keys contain `base64`, they were being **completely removed** from the sanitized settings. + +### Template Logic Impact +Templates check for custom logos using conditions like: +```jinja2 +{% if app_settings.custom_logo_base64 %} + +{% else %} + +{% endif %} +``` + +When `custom_logo_base64` was stripped entirely, this condition always evaluated to `False`, causing the default logo to display instead of the custom uploaded logo. + +## Solution +Modified `sanitize_settings_for_user()` to add boolean flags for logo/favicon existence **after** the main sanitization loop. This allows templates to check if logos exist without exposing the actual base64 data. + +### Code Change +```python +def sanitize_settings_for_user(full_settings: dict) -> dict: + # ... existing sanitization logic ... + + # Add boolean flags for logo/favicon existence so templates can check without exposing base64 data + # These fields are stripped by the base64 filter above, but templates need to know if logos exist + if 'custom_logo_base64' in full_settings: + sanitized['custom_logo_base64'] = bool(full_settings.get('custom_logo_base64')) + if 'custom_logo_dark_base64' in full_settings: + sanitized['custom_logo_dark_base64'] = bool(full_settings.get('custom_logo_dark_base64')) + if 'custom_favicon_base64' in full_settings: + sanitized['custom_favicon_base64'] = bool(full_settings.get('custom_favicon_base64')) + + return sanitized +``` + +### How It Works +1. The sensitive base64 data is still stripped during the main loop +2. After sanitization, boolean flags are added: + - `True` if the logo exists (base64 string is non-empty) + - `False` if no logo is set (base64 string is empty) +3. Templates can still use `{% if app_settings.custom_logo_base64 %}` and it will correctly evaluate to `True` or `False` +4. The actual base64 data is never exposed to the frontend + +## Files Modified +- [functions_settings.py](../../application/single_app/functions_settings.py) - Modified `sanitize_settings_for_user()` function + +## Version +**Fixed in version:** 0.237.002 + +## Testing +A functional test was created: [test_custom_logo_sanitization_fix.py](../../functional_tests/test_custom_logo_sanitization_fix.py) + +### Test Cases +1. **Logo flags preserved as True** - When logos exist, boolean flags are `True` +2. **Logo flags preserved as False** - When logos are empty, boolean flags are `False` +3. **No spurious flags added** - If logo keys don't exist in settings, they're not added +4. **Template compatibility** - Boolean flags work correctly in Jinja2-style conditionals + +### Running the Test +```bash +cd functional_tests +python test_custom_logo_sanitization_fix.py +``` + +## Impact +This fix affects all pages that display the application logo: +- Landing/Index page +- Chat page +- Sidebar navigation (when left nav is enabled) +- Any other page using `base.html` that references logo settings + +## Security Considerations +- āœ… Actual base64 data is still never exposed to the frontend +- āœ… Only boolean True/False values are sent +- āœ… No sensitive data leakage +- āœ… Maintains the security intent of the original sanitization function diff --git a/docs/explanation/release_notes.md b/docs/explanation/release_notes.md index df88ebcd..3b3de6e6 100644 --- a/docs/explanation/release_notes.md +++ b/docs/explanation/release_notes.md @@ -1,6 +1,28 @@ # Feature Release +### **(v0.237.003)** + +#### New Features + +* **Extended Retention Policy Timeline Options** + * Added additional granular retention period options for conversations and documents across all workspace types. + * **New Options**: 2 days, 3 days, 4 days, 6 days, 7 days (1 week), and 14 days (2 weeks). + * **Full Option Set**: 1, 2, 3, 4, 5, 6, 7 (1 week), 10, 14 (2 weeks), 21 (3 weeks), 30, 60, 90 (3 months), 180 (6 months), 365 (1 year), 730 (2 years) days. + * **Scope**: Available in Admin Settings (organization defaults), Profile page (personal settings), and Control Center (group/public workspace management). + * **Files Modified**: `admin_settings.html`, `profile.html`, `control_center.html`. + * (Ref: retention policy configuration, workspace retention settings, granular time periods) + +#### Bug Fixes + +* **Custom Logo Not Displaying Across App Fix** + * Fixed issue where custom logos uploaded via Admin Settings would only display on the admin page but not on other pages (chat, sidebar, landing page). + * **Root Cause**: The `sanitize_settings_for_user()` function was stripping `custom_logo_base64`, `custom_logo_dark_base64`, and `custom_favicon_base64` keys entirely because they contained "base64" (a sensitive term filter), preventing templates from detecting logo existence. + * **Solution**: Modified sanitization to add boolean flags for logo/favicon existence after filtering, allowing templates to check if logos exist without exposing actual base64 data. + * **Security**: Actual base64 data remains hidden from frontend; only True/False boolean values are exposed. + * **Files Modified**: `functions_settings.py` (`sanitize_settings_for_user()` function). + * (Ref: logo display, settings sanitization, template conditionals) + ### **(v0.237.001)** #### New Features diff --git a/functional_tests/test_custom_logo_sanitization_fix.py b/functional_tests/test_custom_logo_sanitization_fix.py new file mode 100644 index 00000000..419a7a1f --- /dev/null +++ b/functional_tests/test_custom_logo_sanitization_fix.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 +""" +Functional test for custom logo sanitization fix. +Version: 0.237.002 +Implemented in: 0.237.002 + +This test ensures that custom logo boolean flags are preserved in sanitized settings +so templates can detect if custom logos exist without exposing the actual base64 data. + +Issue: When a logo was uploaded via admin settings, it was visible on the admin page +but not on other pages (like the chat page) because the `sanitize_settings_for_user` +function was stripping `custom_logo_base64`, `custom_logo_dark_base64`, and +`custom_favicon_base64` keys entirely, which templates use to conditionally display logos. + +Fix: Modified `sanitize_settings_for_user` to add boolean flags for logo/favicon +existence after sanitization, allowing templates to check `app_settings.custom_logo_base64` +(which will be True/False) without exposing the actual base64 data. +""" + +import sys +import os + +# Add the application directory to the path +sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'application', 'single_app')) + + +def test_sanitize_settings_preserves_logo_flags(): + """ + Test that sanitize_settings_for_user preserves boolean flags for logo existence. + """ + print("šŸ” Testing sanitize_settings_for_user preserves logo flags...") + + try: + from functions_settings import sanitize_settings_for_user + + # Test case 1: Settings with custom logos present + settings_with_logos = { + 'app_title': 'Test App', + 'show_logo': True, + 'custom_logo_base64': 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==', + 'custom_logo_dark_base64': 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==', + 'custom_favicon_base64': 'AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAAA==', + 'logo_version': 5, + 'some_api_key': 'secret-key-123', + 'azure_openai_key': 'another-secret-key', + } + + sanitized = sanitize_settings_for_user(settings_with_logos) + + # Verify non-sensitive fields are preserved + assert sanitized.get('app_title') == 'Test App', "app_title should be preserved" + assert sanitized.get('show_logo') == True, "show_logo should be preserved" + assert sanitized.get('logo_version') == 5, "logo_version should be preserved" + + # Verify sensitive keys are removed (api keys, secrets) + assert 'some_api_key' not in sanitized, "API keys should be removed" + assert 'azure_openai_key' not in sanitized, "Azure OpenAI key should be removed" + + # Verify logo flags are boolean True (not the actual base64 data) + assert sanitized.get('custom_logo_base64') == True, "custom_logo_base64 should be True (boolean flag)" + assert sanitized.get('custom_logo_dark_base64') == True, "custom_logo_dark_base64 should be True (boolean flag)" + assert sanitized.get('custom_favicon_base64') == True, "custom_favicon_base64 should be True (boolean flag)" + + # Verify the actual base64 data is NOT exposed + assert isinstance(sanitized.get('custom_logo_base64'), bool), "custom_logo_base64 should be a boolean, not a string" + + print("āœ… Test 1 passed: Logo flags are preserved as boolean True when logos exist") + + # Test case 2: Settings without custom logos + settings_without_logos = { + 'app_title': 'Test App', + 'show_logo': True, + 'custom_logo_base64': '', + 'custom_logo_dark_base64': '', + 'custom_favicon_base64': '', + } + + sanitized2 = sanitize_settings_for_user(settings_without_logos) + + # Verify logo flags are boolean False when logos are empty + assert sanitized2.get('custom_logo_base64') == False, "custom_logo_base64 should be False when empty" + assert sanitized2.get('custom_logo_dark_base64') == False, "custom_logo_dark_base64 should be False when empty" + assert sanitized2.get('custom_favicon_base64') == False, "custom_favicon_base64 should be False when empty" + + print("āœ… Test 2 passed: Logo flags are False when logos are empty/not set") + + # Test case 3: Settings without logo keys at all + settings_no_logo_keys = { + 'app_title': 'Test App', + 'show_logo': False, + } + + sanitized3 = sanitize_settings_for_user(settings_no_logo_keys) + + # Verify logo keys are not added if they didn't exist + assert 'custom_logo_base64' not in sanitized3, "custom_logo_base64 should not be added if not in original settings" + + print("āœ… Test 3 passed: Logo flags are not added if keys not in original settings") + + print("\nāœ… All tests passed!") + return True + + except AssertionError as e: + print(f"āŒ Assertion failed: {e}") + import traceback + traceback.print_exc() + return False + except Exception as e: + print(f"āŒ Test failed with exception: {e}") + import traceback + traceback.print_exc() + return False + + +def test_template_compatibility(): + """ + Test that the boolean flags work correctly in Jinja2-style conditionals. + """ + print("\nšŸ” Testing template compatibility with boolean flags...") + + try: + from functions_settings import sanitize_settings_for_user + + settings = { + 'custom_logo_base64': 'some-base64-data', + 'custom_logo_dark_base64': '', + } + + sanitized = sanitize_settings_for_user(settings) + + # Simulate Jinja2 conditional: {% if app_settings.custom_logo_base64 %} + if sanitized.get('custom_logo_base64'): + light_logo_condition = "show custom light logo" + else: + light_logo_condition = "show default light logo" + + assert light_logo_condition == "show custom light logo", "Light logo should use custom" + + # Simulate Jinja2 conditional: {% if app_settings.custom_logo_dark_base64 %} + if sanitized.get('custom_logo_dark_base64'): + dark_logo_condition = "show custom dark logo" + else: + dark_logo_condition = "show default dark logo" + + assert dark_logo_condition == "show default dark logo", "Dark logo should use default (empty base64)" + + print("āœ… Template compatibility test passed!") + return True + + except Exception as e: + print(f"āŒ Template compatibility test failed: {e}") + import traceback + traceback.print_exc() + return False + + +if __name__ == "__main__": + results = [] + + print("=" * 60) + print("Custom Logo Sanitization Fix - Functional Tests") + print("=" * 60) + + results.append(test_sanitize_settings_preserves_logo_flags()) + results.append(test_template_compatibility()) + + print("\n" + "=" * 60) + success = all(results) + print(f"šŸ“Š Results: {sum(results)}/{len(results)} tests passed") + print("=" * 60) + + sys.exit(0 if success else 1)