The @router.get("/{user_id:path}") decorator was accidentally dropped from the
get_user function when the /local/... routes were inserted above it in the
previous PR. Without the decorator the function was never registered as a GET
handler, so GET /api/admin/users/<id> matched the PUT/DELETE catch-all routes
and Starlette correctly returned 405 Method Not Allowed instead of 200/403.
Adding the decorator back restores the GET endpoint and fixes the 5 tests that
were failing with 405.
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
- Create app/templates/email/default.html (fixes 'default.html not found' error)
- Add DEST_EMAIL_* settings to app/config.py (decoupled from shared EMAIL_* settings)
- Update upload_to_email task to use dest_email_* settings exclusively
- Update _should_upload_to_email() to check dest_email_* settings
- Update config validator, providers, and settings_service for dest_email_*
- Update .env.demo and docs/ConfigurationGuide.md
- Update all tests to use dest_email_* settings where appropriate"
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
The common.js fetch('/api/auth/whoami') probe on every page load was
overwriting the redirect_after_login session key with the API endpoint URL.
After login, users were sent to the JSON endpoint instead of the original page.
Fix: require_login now returns HTTP 401 for any /api/* path, consistent
with REST conventions, and never stores API URLs as the post-login redirect.
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
- Move `import os` to top-level in app/views/backup.py
- Fix docstring in BackupRecord model to remove non-existent 'location' field
- Replace browser confirm() dialogs with accessible modal dialog (role=dialog, aria-modal, aria-labelledby)
- Add csrfToken() helper that validates token presence instead of silently falling back to empty string
- Fix aria-live region to remain in DOM (screen-reader friendly) rather than using x-show
- Add Backup & Restore section to docs/ConfigurationGuide.md with retention table
- Add backup env vars to .env.demo with comments
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
When ADMIN_USERNAME/ADMIN_PASSWORD env vars are not configured, settings
values are None. Python's `None == None` evaluates to True, so any login
request omitting those form fields was authenticated as admin — creating a
phantom 'None@local.docuelevate' profile with admin rights and business plan.
Guard the admin credential check to require both values to be truthy
(non-None, non-empty) before attempting the comparison.
Adds three regression tests covering: both None, both empty-string, and
only password None scenarios.
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
The "Block this user" and "Complimentary plan" toggle switches were always
greyed out because the app loads Tailwind CSS v2.2.19 from CDN, but the
toggles used Tailwind v3-only features (peer, peer-checked:*, after:content-[''],
arbitrary value syntax like after:top-[2px], etc.).
Replaced both toggles with button[role=switch] elements driven by Alpine.js
@click handlers and :class bindings — fully compatible with Tailwind v2.
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>