MetaList 0.3.11__tar.gz → 0.3.12__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {metalist-0.3.11 → metalist-0.3.12}/PKG-INFO +49 -7
- {metalist-0.3.11 → metalist-0.3.12}/README.md +44 -6
- {metalist-0.3.11 → metalist-0.3.12}/app/api/middleware/auth.py +26 -26
- {metalist-0.3.11 → metalist-0.3.12}/app/api/request_auth.py +12 -1
- {metalist-0.3.11 → metalist-0.3.12}/app/api/routes/auth.py +7 -14
- {metalist-0.3.11 → metalist-0.3.12}/app/api/routes/backups.py +15 -13
- {metalist-0.3.11 → metalist-0.3.12}/app/api/routes/notes.py +21 -2
- metalist-0.3.12/app/api/routes/remote_images.py +88 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/config.py +5 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/db/file_session.py +2 -2
- {metalist-0.3.11 → metalist-0.3.12}/app/db/migrations.py +13 -3
- {metalist-0.3.11 → metalist-0.3.12}/app/db/schema.py +24 -0
- metalist-0.3.12/app/db/version.py +3 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/encryption_audit.py +123 -8
- {metalist-0.3.11 → metalist-0.3.12}/app/main.py +60 -11
- {metalist-0.3.11 → metalist-0.3.12}/app/models/database.py +2 -2
- {metalist-0.3.11 → metalist-0.3.12}/app/models/note_crud.py +6 -4
- {metalist-0.3.11 → metalist-0.3.12}/app/models/utils.py +22 -19
- metalist-0.3.12/app/security/http_headers.py +43 -0
- metalist-0.3.12/app/security/note_html.py +204 -0
- metalist-0.3.12/app/security/request_boundary.py +244 -0
- metalist-0.3.12/app/security/sensitive_logging.py +14 -0
- metalist-0.3.12/app/security/shell_execution.py +40 -0
- metalist-0.3.12/app/security/validation_errors.py +29 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/server_runtime.py +16 -6
- {metalist-0.3.11 → metalist-0.3.12}/app/services/auth_service.py +43 -8
- {metalist-0.3.11 → metalist-0.3.12}/app/services/base_service.py +5 -2
- {metalist-0.3.11 → metalist-0.3.12}/app/services/content_cache.py +4 -1
- {metalist-0.3.11 → metalist-0.3.12}/app/services/content_formatting.py +290 -35
- {metalist-0.3.11 → metalist-0.3.12}/app/services/diagnostics.py +36 -28
- {metalist-0.3.11 → metalist-0.3.12}/app/services/embedded_references.py +11 -1
- {metalist-0.3.11 → metalist-0.3.12}/app/services/link_titles.py +207 -22
- {metalist-0.3.11 → metalist-0.3.12}/app/services/namespace_switcher.py +52 -12
- metalist-0.3.12/app/services/remote_image_proxy.py +299 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/runtime_lock.py +2 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/search_index.py +0 -3
- {metalist-0.3.11 → metalist-0.3.12}/app/services/snapshot.py +0 -1
- {metalist-0.3.11 → metalist-0.3.12}/app/services/transaction_manager.py +6 -3
- {metalist-0.3.11 → metalist-0.3.12}/app/static/css/main.css +106 -36
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/main.js +3 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/api-client.js +9 -35
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/auth.js +24 -17
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/client-state-api.js +17 -2
- metalist-0.3.12/app/static/js/modules/command-palette/activation-auth-policy.js +27 -0
- metalist-0.3.12/app/static/js/modules/command-palette/backup-command-availability.js +49 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/command-palette/command-palette-controller.js +28 -7
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/config.js +2 -2
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/connectivity-monitor.js +1 -1
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/context-menu/context-menu-registry.js +60 -26
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/context-menu/context-menu-service.js +12 -7
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/dom-utils-fixed.js +0 -1
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/dom-utils.js +0 -1
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/error-handler.js +4 -4
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/login-namespace-picker.js +25 -7
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/backup-settings-modal.js +69 -28
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/base-modal.js +1 -2
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/delete-namespace-modal.js +55 -17
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/delete-namespace-validation.js +19 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/namespace-loading-page.js +14 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/namespace-modals.js +20 -14
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/ontology-modal.js +1 -1
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/password-modal.js +15 -8
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/random-password-modal.js +150 -10
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/actions/content-actions.js +3 -2
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/actions/note-actions.js +3 -1
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/actions/ui-actions.js +2 -1
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/events/context-menu-events.js +24 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/mode-context.js +1 -1
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/mode-logger.js +7 -12
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/add-style-service.js +1 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/differential-view-service.js +45 -8
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/html-paste-sanitizer-service.js +10 -1
- metalist-0.3.12/app/static/js/modules/mode-manager/services/remote-image-proxy-service.js +216 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/tag-syntax-service.js +36 -13
- metalist-0.3.12/app/static/js/modules/note-html-sanitizer.js +193 -0
- metalist-0.3.12/app/static/js/modules/password-policy.js +23 -0
- metalist-0.3.12/app/static/js/modules/password-strength.js +81 -0
- metalist-0.3.12/app/static/js/vendor/dompurify-3.4.12.LICENSE.txt +202 -0
- metalist-0.3.12/app/static/js/vendor/dompurify-3.4.12.min.js +3 -0
- metalist-0.3.12/app/static/js/vendor/zxcvbn-4.4.2.LICENSE.txt +20 -0
- metalist-0.3.12/app/static/js/vendor/zxcvbn-4.4.2.js +28 -0
- metalist-0.3.12/app/static/note-html-policy.json +99 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/templates/base.html +2 -1
- {metalist-0.3.11 → metalist-0.3.12}/app/templates/locked.html +1 -1
- {metalist-0.3.11 → metalist-0.3.12}/app/templates/maintenance.html +1 -1
- {metalist-0.3.11 → metalist-0.3.12}/app/templates/namespace_deleted.html +1 -1
- {metalist-0.3.11 → metalist-0.3.12}/app/templates/namespace_renamed.html +1 -1
- {metalist-0.3.11 → metalist-0.3.12}/app/usecases/create_note.py +6 -2
- {metalist-0.3.11 → metalist-0.3.12}/app/usecases/delete_subtree.py +14 -3
- {metalist-0.3.11 → metalist-0.3.12}/app/usecases/run_shell.py +18 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/usecases/split_note.py +5 -3
- {metalist-0.3.11 → metalist-0.3.12}/app/usecases/update_content.py +8 -5
- {metalist-0.3.11 → metalist-0.3.12}/app/version.py +1 -1
- {metalist-0.3.11 → metalist-0.3.12}/main.py +90 -32
- {metalist-0.3.11 → metalist-0.3.12}/metalist.egg-info/PKG-INFO +49 -7
- {metalist-0.3.11 → metalist-0.3.12}/metalist.egg-info/SOURCES.txt +20 -0
- {metalist-0.3.11 → metalist-0.3.12}/metalist.egg-info/requires.txt +4 -0
- {metalist-0.3.11 → metalist-0.3.12}/pyproject.toml +4 -0
- {metalist-0.3.11 → metalist-0.3.12}/MANIFEST.in +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/__init__.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/api/__init__.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/api/deps.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/api/dev.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/api/routes/files.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/api/routes/ontology.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/api/routes/reminders.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/api/routes/sounds.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/api/routes/test.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/api/transactions.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/db/__init__.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/db/backup_settings_sql.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/db/engine.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/db/file_schema.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/db/files_sql.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/db/link_titles_sql.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/db/notes_sql.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/db/ontology_rules_sql.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/db/reminders_sql.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/db/search_history_sql.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/db/session.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/db/settings_sql.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/db/sounds_sql.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/db/tab_state_sql.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/models/commands.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/models/entities.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/models/enums.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/models/linked_list.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/models/list_operations.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/models/list_traversal.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/presentation/__init__.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/presentation/render/__init__.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/presentation/render/note_renderer.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/presentation/templates.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/security/encryption.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/__init__.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/auth.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/auth_cache_state.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/backlinks.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/backup_service.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/backup_settings_service.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/client_state_service.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/date_filtering.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/dependencies.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/encryption.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/exception_capture.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/file_registry.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/file_storage.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/html_export.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/html_unformatting.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/hydration_state.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/image_size_formatting.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/inline_image_occurrences.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/integrity.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/latex_rendering.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/login_rate_limit.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/maintenance_mode.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/markdown_rendering.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/namespace_deletion_jobs.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/namespace_deletion_worker.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/namespace_rename_jobs.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/namespace_rename_worker.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/note_image_tags.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/note_service.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/note_store.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/ontology_rules_store.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/query_service.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/reminders.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/root_sorting.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/runtime_hardening.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/search_history.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/search_query.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/search_text.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/selected_text_tag.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/self_update.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/session_timeout_service.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/shell_session_service.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/sound_storage.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/store.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/sync.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/sync_state.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/tab_state.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/tag_case.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/tag_ontology.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/tag_rename.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/tag_suggestions.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/tag_term_matching.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/test_reset.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/tokens.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/undo_service.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/undo_state.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/view_cache.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/view_diff.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/view_state.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/services/windows_process_control.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/startup_js_sanity.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/startup_sanity.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/startup_sanity_config.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/config/command_palette_tags.json +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/css/forms.css +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/img/favicon.ico +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/locked.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/activity-tracker.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/async-result.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/client-state-migration.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/command-palette/click-activation-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/command-palette/endpoint-registry.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/command-palette/note-layout-preferences.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/command-palette/preferences-store.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/command-palette/tag-config-loader.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/command-palette/usage-store.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/context-menu/context-menu-target-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/context-menu/input-caret-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/editor-commands.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/editor-selection.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/editor-toolbar.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/error-overlay.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/location-flags.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/alphabetize-root-notes-modal.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/backup-restore-modal.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/backup-result-modal.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/backup-retention-modal.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/help-modal.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/namespace-ports-profile.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/namespace-rename-validation.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/note-layout-appearance-modal.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/prioritize-modal.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/reminder-modal.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/reset-updated-at-modal.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/session-timeout-modal.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/sound-manager-modal.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/version-info-modal.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/actions/history-actions.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/actions/search-actions.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/actions/selection-actions.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/events/focus-events.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/events/inactivity-events.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/events/input-events.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/events/keyboard-events.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/events/mouse-events.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/events/search-events.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/mode-manager-controller.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/animated-scroll-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/backlinks-panel-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/clipboard-shortcut-policy-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/collapse-affordance-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/collapse-editing-policy-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/command-gate-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/context-menu-target-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/date-filter-indicator-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/date-filter-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/edit-session-collapse-policy-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/edit-session-collapse-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/embedded-image-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/file-drop-policy-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/file-image-preview-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/file-reference-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/filtered-refresh-selection-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/image-context-menu-action-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/image-file-insert-choice-modal-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/infinite-scroll-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/link-context-menu-action-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/markdown-render-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/note-click-target-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/note-clipboard-write-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/note-collapse-animation-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/note-context-menu-action-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/note-delete-shortcut-policy-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/note-drag-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/note-reposition-animation-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/note-split-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/password-clipboard-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/polling-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/remove-formatting-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/rhs-panel-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/root-date-separator-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/root-sort-indicator-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/root-sort-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/scroll-anchor-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/scroll-restoration-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/scroll-to-top-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/search-contexts-overlay-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/search-debounce-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/search-input-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/search-interaction-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/search-redaction-reveal-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/search-suggestions-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/search-syntax-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/selected-formatting-removal-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/selected-text-tag-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/shell-output-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/tab-dom-cache-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/tab-duplication-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/tab-state-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/tag-bar-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/tag-suggestions-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/todo-toggle-editing-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/view-mode-search-shortcut-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/password-generator.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/reminder-store.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/reminder-surface-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/selection-markers.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/session-auth.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/sound-service.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/tag-token.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/uuid.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/namespace-deleted.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/namespace-renamed.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/js/vendor/markdown-it.min.js +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/static/video/login-startup.mp4 +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/templates/index.html +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/templates/notes_list.html +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/usecases/__init__.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/usecases/add_selected_text_tag.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/usecases/alphabetize_root_notes.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/usecases/base.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/usecases/collapse.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/usecases/copy_note.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/usecases/create_child.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/usecases/create_sibling.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/usecases/expand.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/usecases/indent.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/usecases/lock.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/usecases/move.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/usecases/move_to_top.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/usecases/outdent.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/usecases/paste_child.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/usecases/paste_sibling.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/usecases/prioritize.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/usecases/record_edit_mode.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/usecases/redo.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/usecases/rename_tag.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/usecases/reset_updated_at.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/usecases/resize_image.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/usecases/search_comment_autofill.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/usecases/search_context_tags.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/usecases/set_collapse_bulk.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/usecases/set_collapse_in_context.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/usecases/toggle_reference_mode.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/usecases/toggle_todo_done.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/usecases/undo.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/usecases/unformat_content.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/usecases/view.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/utils/__init__.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/utils/encryption.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/app/utils/text_utils.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/convert-from-legacy.py +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/metalist.egg-info/dependency_links.txt +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/metalist.egg-info/entry_points.txt +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/metalist.egg-info/top_level.txt +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/scripts/generate-lan-cert.sh +0 -0
- {metalist-0.3.11 → metalist-0.3.12}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: metalist
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.12
|
|
4
4
|
Summary: A minimalist single-user note-taking app with SSR, fast in-memory tree operations, and efficient sync.
|
|
5
5
|
Project-URL: Homepage, https://github.com/evolvingstuff/metalist
|
|
6
6
|
Project-URL: Repository, https://github.com/evolvingstuff/metalist
|
|
@@ -10,11 +10,14 @@ Description-Content-Type: text/markdown
|
|
|
10
10
|
Requires-Dist: argon2-cffi==25.1.0
|
|
11
11
|
Requires-Dist: cryptography==50.0.0
|
|
12
12
|
Requires-Dist: fastapi==0.141.1
|
|
13
|
+
Requires-Dist: httpcore==1.0.9
|
|
13
14
|
Requires-Dist: httpx==0.28.1
|
|
14
15
|
Requires-Dist: latex2mathml==3.81.0
|
|
15
16
|
Requires-Dist: loguru==0.7.3
|
|
16
17
|
Requires-Dist: Mako==1.3.12
|
|
17
18
|
Requires-Dist: mutagen==1.48.1
|
|
19
|
+
Requires-Dist: nh3==0.3.6
|
|
20
|
+
Requires-Dist: pillow==12.3.0
|
|
18
21
|
Requires-Dist: pydantic==2.13.4
|
|
19
22
|
Requires-Dist: python-multipart==0.0.32
|
|
20
23
|
Requires-Dist: starlette==1.3.1
|
|
@@ -22,6 +25,7 @@ Requires-Dist: tqdm==4.70.0
|
|
|
22
25
|
Requires-Dist: tree-sitter==0.25.2
|
|
23
26
|
Requires-Dist: tree-sitter-javascript==0.25.0
|
|
24
27
|
Requires-Dist: uvicorn==0.52.0
|
|
28
|
+
Requires-Dist: zxcvbn==4.5.0
|
|
25
29
|
Provides-Extra: dev
|
|
26
30
|
Requires-Dist: hypothesis==6.164.0; extra == "dev"
|
|
27
31
|
Requires-Dist: pytest==9.1.1; extra == "dev"
|
|
@@ -99,8 +103,22 @@ metalist
|
|
|
99
103
|
```
|
|
100
104
|
For source-checkout compatibility, `python main.py` performs the same orchestration. Use `metalist work`, `python main.py --namespace work`, or `python main.py work` when you want one foreground namespace process.
|
|
101
105
|
|
|
102
|
-
|
|
103
|
-
|
|
106
|
+
Shell execution is disabled by default. To enable `@shell` for every namespace
|
|
107
|
+
started by the top-level orchestrator, use either:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
metalist --enable-shell
|
|
111
|
+
python main.py --enable-shell
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
This flag is propagated to every namespace child process. The top-level process
|
|
115
|
+
prints a conspicuous shell-enabled banner before its namespace launch results.
|
|
116
|
+
Use `python main.py work --enable-shell` for one foreground namespace. Shell
|
|
117
|
+
routes remain restricted to loopback clients using a loopback request host,
|
|
118
|
+
even when the rest of MetaList is intentionally exposed to a LAN.
|
|
119
|
+
|
|
120
|
+
`metalist` and explicit single-namespace source runs bind to loopback at `127.0.0.1:8000` by default. This keeps a normal laptop launch off LAN and public interfaces unless remote access is explicitly configured.
|
|
121
|
+
On first startup, MetaList also auto-generates a self-signed TLS pair at `~/MetaList/certs/metalist-cert.pem` and `~/MetaList/certs/metalist-key.pem`, then enables HTTPS on the same bind host at port `8443`. If you already have real PEM files, point `METALIST_TLS_CERT` and `METALIST_TLS_KEY` at them instead. Set `METALIST_AUTO_GENERATE_TLS=0` only if you explicitly want HTTP-only startup.
|
|
104
122
|
|
|
105
123
|
Database selection:
|
|
106
124
|
- No explicit namespace on a single-namespace launch: `~/MetaList/namespaces/default/default.metalist.db`
|
|
@@ -116,20 +134,36 @@ Useful env flags:
|
|
|
116
134
|
- `CRASH_SERVER_ON_FAIL=1` (default): fail-fast on validation errors
|
|
117
135
|
- `API_PREFIX=/api2`: override API prefix (client assumes `/api2` by default)
|
|
118
136
|
- `METALIST_NAMESPACE=work`: select `~/MetaList/namespaces/work/work.metalist.db`
|
|
119
|
-
- `METALIST_HOST=
|
|
137
|
+
- `METALIST_HOST=127.0.0.1` (default): bind the main app to a specific interface; use a LAN IP or `0.0.0.0` only for intentional remote access
|
|
138
|
+
- `METALIST_ALLOWED_HOSTS=notes.example.com,192.168.1.20`: comma-separated public/LAN hostnames accepted in HTTP `Host` headers; loopback aliases and a specific non-wildcard `METALIST_HOST` are accepted automatically
|
|
120
139
|
- `METALIST_PORT=8000` (default): bind the main app to a different port
|
|
121
140
|
- `METALIST_HTTPS_PORT=8443`: override the HTTPS port when TLS is enabled
|
|
122
141
|
- `METALIST_TLS_CERT=/path/to/fullchain.pem` + `METALIST_TLS_KEY=/path/to/privkey.pem`: override TLS paths
|
|
123
142
|
- `METALIST_AUTO_GENERATE_TLS=0`: disable automatic creation of the default self-signed TLS pair
|
|
124
143
|
- default TLS paths: `~/MetaList/certs/metalist-cert.pem` and `~/MetaList/certs/metalist-key.pem`
|
|
125
144
|
- `METALIST_FORWARDED_ALLOW_IPS=127.0.0.1,::1` (default): trust proxy headers only from those reverse-proxy IPs
|
|
145
|
+
- `--enable-shell`: opt in to local `@shell` execution for this launch; shell routes require a loopback client and loopback request host, and the capability is never persisted in namespace data
|
|
126
146
|
|
|
127
147
|
### Remote Access / HTTPS
|
|
128
|
-
|
|
148
|
+
LAN or VPN access must be enabled explicitly. Prefer binding the machine's specific LAN address, which also adds that address to the accepted-host set:
|
|
129
149
|
```bash
|
|
130
|
-
metalist
|
|
150
|
+
METALIST_HOST=192.168.1.20 metalist
|
|
131
151
|
```
|
|
132
|
-
On a fresh machine, that first launch also creates the default TLS cert pair automatically. Then open either `http
|
|
152
|
+
On a fresh machine, that first launch also creates the default TLS cert pair automatically. Then open either `http://192.168.1.20:8000` or `https://192.168.1.20:8443` from the other machine.
|
|
153
|
+
|
|
154
|
+
To keep `@shell` available on the host laptop while allowing another laptop to
|
|
155
|
+
use normal MetaList features, bind both interfaces and explicitly allow the LAN
|
|
156
|
+
address:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
METALIST_HOST=0.0.0.0 \
|
|
160
|
+
METALIST_ALLOWED_HOSTS=10.0.0.31 \
|
|
161
|
+
metalist --enable-shell
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Use `http://127.0.0.1:<namespace-http-port>` on the host laptop when running
|
|
165
|
+
`@shell`. Other devices may use `https://10.0.0.31:<namespace-https-port>`, but
|
|
166
|
+
their shell start/status requests receive `403`.
|
|
133
167
|
|
|
134
168
|
Namespaced launch example:
|
|
135
169
|
```bash
|
|
@@ -147,6 +181,7 @@ and MetaList will reuse the saved HTTP / HTTPS ports for `work`. The same applie
|
|
|
147
181
|
Equivalent explicit launch, if you want it:
|
|
148
182
|
```bash
|
|
149
183
|
METALIST_HOST=0.0.0.0 \
|
|
184
|
+
METALIST_ALLOWED_HOSTS=192.168.1.20 \
|
|
150
185
|
METALIST_PORT=8000 \
|
|
151
186
|
METALIST_HTTPS_PORT=8443 \
|
|
152
187
|
metalist
|
|
@@ -156,6 +191,7 @@ From the other machine, open `https://<laptop-ip>:8443`.
|
|
|
156
191
|
If you already have a real certificate and key, use the same dual-listener flow:
|
|
157
192
|
```bash
|
|
158
193
|
METALIST_HOST=0.0.0.0 \
|
|
194
|
+
METALIST_ALLOWED_HOSTS=192.168.1.20 \
|
|
159
195
|
METALIST_PORT=8000 \
|
|
160
196
|
METALIST_HTTPS_PORT=8443 \
|
|
161
197
|
METALIST_TLS_CERT=/path/to/fullchain.pem \
|
|
@@ -175,11 +211,17 @@ When HTTPS is enabled:
|
|
|
175
211
|
If TLS is terminated by a reverse proxy on the same machine instead, keep MetaList on loopback and let the proxy forward to it:
|
|
176
212
|
```bash
|
|
177
213
|
METALIST_HOST=127.0.0.1 \
|
|
214
|
+
METALIST_ALLOWED_HOSTS=notes.example.com \
|
|
178
215
|
METALIST_PORT=8000 \
|
|
179
216
|
METALIST_FORWARDED_ALLOW_IPS=127.0.0.1,::1 \
|
|
180
217
|
metalist
|
|
181
218
|
```
|
|
182
219
|
|
|
220
|
+
The reverse proxy must preserve the browser-facing `Host` header and set
|
|
221
|
+
`X-Forwarded-Proto`. MetaList never trusts `X-Forwarded-Host`; forwarded
|
|
222
|
+
scheme/client metadata is accepted only from `METALIST_FORWARDED_ALLOW_IPS`.
|
|
223
|
+
Do not widen that list beyond the actual proxy addresses.
|
|
224
|
+
|
|
183
225
|
### Legacy Import
|
|
184
226
|
`convert-from-legacy.py` replaces the SQLite database referenced by `app.config.DATABASE_URL`, clears its files/sounds sidecar, and imports notes from a legacy JSON export.
|
|
185
227
|
|
|
@@ -70,8 +70,22 @@ metalist
|
|
|
70
70
|
```
|
|
71
71
|
For source-checkout compatibility, `python main.py` performs the same orchestration. Use `metalist work`, `python main.py --namespace work`, or `python main.py work` when you want one foreground namespace process.
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
Shell execution is disabled by default. To enable `@shell` for every namespace
|
|
74
|
+
started by the top-level orchestrator, use either:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
metalist --enable-shell
|
|
78
|
+
python main.py --enable-shell
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
This flag is propagated to every namespace child process. The top-level process
|
|
82
|
+
prints a conspicuous shell-enabled banner before its namespace launch results.
|
|
83
|
+
Use `python main.py work --enable-shell` for one foreground namespace. Shell
|
|
84
|
+
routes remain restricted to loopback clients using a loopback request host,
|
|
85
|
+
even when the rest of MetaList is intentionally exposed to a LAN.
|
|
86
|
+
|
|
87
|
+
`metalist` and explicit single-namespace source runs bind to loopback at `127.0.0.1:8000` by default. This keeps a normal laptop launch off LAN and public interfaces unless remote access is explicitly configured.
|
|
88
|
+
On first startup, MetaList also auto-generates a self-signed TLS pair at `~/MetaList/certs/metalist-cert.pem` and `~/MetaList/certs/metalist-key.pem`, then enables HTTPS on the same bind host at port `8443`. If you already have real PEM files, point `METALIST_TLS_CERT` and `METALIST_TLS_KEY` at them instead. Set `METALIST_AUTO_GENERATE_TLS=0` only if you explicitly want HTTP-only startup.
|
|
75
89
|
|
|
76
90
|
Database selection:
|
|
77
91
|
- No explicit namespace on a single-namespace launch: `~/MetaList/namespaces/default/default.metalist.db`
|
|
@@ -87,20 +101,36 @@ Useful env flags:
|
|
|
87
101
|
- `CRASH_SERVER_ON_FAIL=1` (default): fail-fast on validation errors
|
|
88
102
|
- `API_PREFIX=/api2`: override API prefix (client assumes `/api2` by default)
|
|
89
103
|
- `METALIST_NAMESPACE=work`: select `~/MetaList/namespaces/work/work.metalist.db`
|
|
90
|
-
- `METALIST_HOST=
|
|
104
|
+
- `METALIST_HOST=127.0.0.1` (default): bind the main app to a specific interface; use a LAN IP or `0.0.0.0` only for intentional remote access
|
|
105
|
+
- `METALIST_ALLOWED_HOSTS=notes.example.com,192.168.1.20`: comma-separated public/LAN hostnames accepted in HTTP `Host` headers; loopback aliases and a specific non-wildcard `METALIST_HOST` are accepted automatically
|
|
91
106
|
- `METALIST_PORT=8000` (default): bind the main app to a different port
|
|
92
107
|
- `METALIST_HTTPS_PORT=8443`: override the HTTPS port when TLS is enabled
|
|
93
108
|
- `METALIST_TLS_CERT=/path/to/fullchain.pem` + `METALIST_TLS_KEY=/path/to/privkey.pem`: override TLS paths
|
|
94
109
|
- `METALIST_AUTO_GENERATE_TLS=0`: disable automatic creation of the default self-signed TLS pair
|
|
95
110
|
- default TLS paths: `~/MetaList/certs/metalist-cert.pem` and `~/MetaList/certs/metalist-key.pem`
|
|
96
111
|
- `METALIST_FORWARDED_ALLOW_IPS=127.0.0.1,::1` (default): trust proxy headers only from those reverse-proxy IPs
|
|
112
|
+
- `--enable-shell`: opt in to local `@shell` execution for this launch; shell routes require a loopback client and loopback request host, and the capability is never persisted in namespace data
|
|
97
113
|
|
|
98
114
|
### Remote Access / HTTPS
|
|
99
|
-
|
|
115
|
+
LAN or VPN access must be enabled explicitly. Prefer binding the machine's specific LAN address, which also adds that address to the accepted-host set:
|
|
100
116
|
```bash
|
|
101
|
-
metalist
|
|
117
|
+
METALIST_HOST=192.168.1.20 metalist
|
|
102
118
|
```
|
|
103
|
-
On a fresh machine, that first launch also creates the default TLS cert pair automatically. Then open either `http
|
|
119
|
+
On a fresh machine, that first launch also creates the default TLS cert pair automatically. Then open either `http://192.168.1.20:8000` or `https://192.168.1.20:8443` from the other machine.
|
|
120
|
+
|
|
121
|
+
To keep `@shell` available on the host laptop while allowing another laptop to
|
|
122
|
+
use normal MetaList features, bind both interfaces and explicitly allow the LAN
|
|
123
|
+
address:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
METALIST_HOST=0.0.0.0 \
|
|
127
|
+
METALIST_ALLOWED_HOSTS=10.0.0.31 \
|
|
128
|
+
metalist --enable-shell
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Use `http://127.0.0.1:<namespace-http-port>` on the host laptop when running
|
|
132
|
+
`@shell`. Other devices may use `https://10.0.0.31:<namespace-https-port>`, but
|
|
133
|
+
their shell start/status requests receive `403`.
|
|
104
134
|
|
|
105
135
|
Namespaced launch example:
|
|
106
136
|
```bash
|
|
@@ -118,6 +148,7 @@ and MetaList will reuse the saved HTTP / HTTPS ports for `work`. The same applie
|
|
|
118
148
|
Equivalent explicit launch, if you want it:
|
|
119
149
|
```bash
|
|
120
150
|
METALIST_HOST=0.0.0.0 \
|
|
151
|
+
METALIST_ALLOWED_HOSTS=192.168.1.20 \
|
|
121
152
|
METALIST_PORT=8000 \
|
|
122
153
|
METALIST_HTTPS_PORT=8443 \
|
|
123
154
|
metalist
|
|
@@ -127,6 +158,7 @@ From the other machine, open `https://<laptop-ip>:8443`.
|
|
|
127
158
|
If you already have a real certificate and key, use the same dual-listener flow:
|
|
128
159
|
```bash
|
|
129
160
|
METALIST_HOST=0.0.0.0 \
|
|
161
|
+
METALIST_ALLOWED_HOSTS=192.168.1.20 \
|
|
130
162
|
METALIST_PORT=8000 \
|
|
131
163
|
METALIST_HTTPS_PORT=8443 \
|
|
132
164
|
METALIST_TLS_CERT=/path/to/fullchain.pem \
|
|
@@ -146,11 +178,17 @@ When HTTPS is enabled:
|
|
|
146
178
|
If TLS is terminated by a reverse proxy on the same machine instead, keep MetaList on loopback and let the proxy forward to it:
|
|
147
179
|
```bash
|
|
148
180
|
METALIST_HOST=127.0.0.1 \
|
|
181
|
+
METALIST_ALLOWED_HOSTS=notes.example.com \
|
|
149
182
|
METALIST_PORT=8000 \
|
|
150
183
|
METALIST_FORWARDED_ALLOW_IPS=127.0.0.1,::1 \
|
|
151
184
|
metalist
|
|
152
185
|
```
|
|
153
186
|
|
|
187
|
+
The reverse proxy must preserve the browser-facing `Host` header and set
|
|
188
|
+
`X-Forwarded-Proto`. MetaList never trusts `X-Forwarded-Host`; forwarded
|
|
189
|
+
scheme/client metadata is accepted only from `METALIST_FORWARDED_ALLOW_IPS`.
|
|
190
|
+
Do not widen that list beyond the actual proxy addresses.
|
|
191
|
+
|
|
154
192
|
### Legacy Import
|
|
155
193
|
`convert-from-legacy.py` replaces the SQLite database referenced by `app.config.DATABASE_URL`, clears its files/sounds sidecar, and imports notes from a legacy JSON export.
|
|
156
194
|
|
|
@@ -14,23 +14,28 @@ from app.config import API_PREFIX, TEST_MODE
|
|
|
14
14
|
|
|
15
15
|
class AuthMiddleware(BaseHTTPMiddleware):
|
|
16
16
|
"""Middleware to check authentication on protected routes."""
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
|
|
18
|
+
PUBLIC_EXACT_PATHS = frozenset({
|
|
19
|
+
"/",
|
|
20
20
|
f"{API_PREFIX}/auth/login",
|
|
21
|
+
f"{API_PREFIX}/auth/login-namespaces",
|
|
22
|
+
f"{API_PREFIX}/auth/login-namespaces/open",
|
|
21
23
|
f"{API_PREFIX}/auth/status",
|
|
22
24
|
f"{API_PREFIX}/auth/session",
|
|
23
|
-
f"{API_PREFIX}/auth/namespaces/delete-jobs/",
|
|
24
|
-
f"{API_PREFIX}/auth/namespaces/rename-jobs/",
|
|
25
|
-
"/namespace-deleted",
|
|
26
|
-
"/namespace-renamed",
|
|
27
|
-
"/static/", # CSS/JS files needed for login page
|
|
28
25
|
"/favicon.ico",
|
|
29
26
|
"/locked",
|
|
30
|
-
|
|
27
|
+
"/namespace-deleted",
|
|
28
|
+
"/namespace-deleted/open",
|
|
29
|
+
"/namespace-renamed",
|
|
30
|
+
"/namespace-renamed/open",
|
|
31
|
+
} | ({f"{API_PREFIX}/test/reset"} if TEST_MODE else set()))
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
PUBLIC_PREFIX_PATHS = (
|
|
34
|
+
f"{API_PREFIX}/auth/namespaces/delete-jobs/",
|
|
35
|
+
f"{API_PREFIX}/auth/namespaces/rename-jobs/",
|
|
36
|
+
"/static/", # CSS/JS files needed for login page
|
|
37
|
+
)
|
|
38
|
+
assert all(path.endswith("/") for path in PUBLIC_PREFIX_PATHS)
|
|
34
39
|
|
|
35
40
|
# Paths to suppress verbose logging for (frequent polling endpoints)
|
|
36
41
|
QUIET_PATHS = [
|
|
@@ -44,6 +49,14 @@ class AuthMiddleware(BaseHTTPMiddleware):
|
|
|
44
49
|
]
|
|
45
50
|
|
|
46
51
|
# Note: /api/notes/* paths are NOT in this list - they require auth when password is set
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def is_public_path(cls, *, path: str) -> bool:
|
|
55
|
+
if not isinstance(path, str) or not path.startswith("/"):
|
|
56
|
+
raise TypeError("path must be an absolute URL path")
|
|
57
|
+
if path in cls.PUBLIC_EXACT_PATHS:
|
|
58
|
+
return True
|
|
59
|
+
return any(path.startswith(prefix) for prefix in cls.PUBLIC_PREFIX_PATHS)
|
|
47
60
|
|
|
48
61
|
async def dispatch(self, request: Request, call_next):
|
|
49
62
|
"""Check authentication for protected routes."""
|
|
@@ -74,22 +87,9 @@ class AuthMiddleware(BaseHTTPMiddleware):
|
|
|
74
87
|
if not is_quiet:
|
|
75
88
|
print(f"Middleware checking path: {path}")
|
|
76
89
|
|
|
77
|
-
|
|
78
|
-
public_match = None
|
|
79
|
-
|
|
80
|
-
# Handle exact root path match
|
|
81
|
-
if path == "/":
|
|
82
|
-
public_match = "/ (root)"
|
|
83
|
-
else:
|
|
84
|
-
# Check other public paths with startswith
|
|
85
|
-
for public in self.PUBLIC_PATHS:
|
|
86
|
-
if path.startswith(public):
|
|
87
|
-
public_match = public
|
|
88
|
-
break
|
|
89
|
-
|
|
90
|
-
if public_match:
|
|
90
|
+
if self.is_public_path(path=path):
|
|
91
91
|
if not is_quiet:
|
|
92
|
-
print(f"Path {path} is public
|
|
92
|
+
print(f"Path {path} is explicitly public, skipping auth")
|
|
93
93
|
return await call_next(request)
|
|
94
94
|
|
|
95
95
|
if not is_quiet:
|
|
@@ -2,8 +2,19 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from fastapi import HTTPException, Request, Response
|
|
4
4
|
|
|
5
|
+
from app.config import ACTIVE_NAMESPACE
|
|
6
|
+
from app.server_runtime import validate_namespace
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
|
|
9
|
+
AUTH_COOKIE_PREFIX = "metalist_auth"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def auth_cookie_name_for_namespace(namespace: str) -> str:
|
|
13
|
+
normalized_namespace = validate_namespace(namespace=namespace)
|
|
14
|
+
return f"{AUTH_COOKIE_PREFIX}_{normalized_namespace}"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
AUTH_COOKIE_NAME = auth_cookie_name_for_namespace(ACTIVE_NAMESPACE)
|
|
7
18
|
|
|
8
19
|
|
|
9
20
|
def read_request_auth_token(request: Request) -> tuple[str | None, str | None]:
|
|
@@ -56,6 +56,7 @@ from app.server_runtime import load_all_namespace_launch_profiles
|
|
|
56
56
|
from app.server_runtime import resolve_namespace_directory
|
|
57
57
|
from app.server_runtime import resolve_namespaced_database_path
|
|
58
58
|
from app.server_runtime import validate_namespace
|
|
59
|
+
from app.security.sensitive_logging import traceback_frame_summary
|
|
59
60
|
from app.services.encryption import EncryptionService
|
|
60
61
|
from app.services.tokens import token_service
|
|
61
62
|
from app.services.note_store import store as note_store
|
|
@@ -443,11 +444,11 @@ def _on_hydration_done(future: Future) -> None:
|
|
|
443
444
|
return
|
|
444
445
|
error = future.exception()
|
|
445
446
|
if error is not None:
|
|
446
|
-
error_message =
|
|
447
|
+
error_message = type(error).__name__
|
|
447
448
|
logger.error(
|
|
448
|
-
"Hydration worker failed: %s",
|
|
449
|
+
"Hydration worker failed: %s frames=%s",
|
|
449
450
|
error_message,
|
|
450
|
-
|
|
451
|
+
traceback_frame_summary(error.__traceback__),
|
|
451
452
|
)
|
|
452
453
|
hydration_state.fail(error_message)
|
|
453
454
|
|
|
@@ -540,17 +541,9 @@ def _client_info(request: Request) -> str:
|
|
|
540
541
|
|
|
541
542
|
|
|
542
543
|
def _login_rate_limit_key(request: Request) -> str:
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
if forwarded_for:
|
|
547
|
-
first_hop = forwarded_for.split(",")[0].strip()
|
|
548
|
-
if first_hop:
|
|
549
|
-
return f"ip:{first_hop}"
|
|
550
|
-
if request.client and request.client.host:
|
|
551
|
-
return f"ip:{request.client.host}"
|
|
552
|
-
user_agent = request.headers.get("user-agent", "Unknown")
|
|
553
|
-
return f"ua:{user_agent[:128]}"
|
|
544
|
+
if request.client is None or request.client.host == "":
|
|
545
|
+
raise RuntimeError("Login request is missing client metadata")
|
|
546
|
+
return f"ip:{request.client.host}"
|
|
554
547
|
|
|
555
548
|
|
|
556
549
|
def _require_tab_id(
|
|
@@ -870,19 +870,21 @@ def list_backups(token: Annotated[str, Depends(_require_auth)]):
|
|
|
870
870
|
|
|
871
871
|
@router.post("/run", response_model=BackupRunResponse)
|
|
872
872
|
@transactional_route
|
|
873
|
-
def run_backup(
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
if not isinstance(selected_namespaces, list):
|
|
881
|
-
raise RuntimeError("backup settings selected_namespaces must be a list")
|
|
882
|
-
if not isinstance(retention_count, int) or retention_count <= 0:
|
|
883
|
-
raise RuntimeError("backup settings retention_count must be a positive integer")
|
|
873
|
+
def run_backup(
|
|
874
|
+
payload: BackupSettingsUpdateRequest,
|
|
875
|
+
token: Annotated[str, Depends(_require_auth)],
|
|
876
|
+
):
|
|
877
|
+
folder_path = _prepare_folder_backup_directory_for_settings(
|
|
878
|
+
folder_path=payload.folder_path,
|
|
879
|
+
)
|
|
884
880
|
normalized_selected_namespaces = _filter_deleted_selected_namespaces(
|
|
885
|
-
selected_namespaces=selected_namespaces,
|
|
881
|
+
selected_namespaces=payload.selected_namespaces,
|
|
882
|
+
)
|
|
883
|
+
update_backup_settings(
|
|
884
|
+
token=token,
|
|
885
|
+
folder_path=folder_path,
|
|
886
|
+
selected_namespaces=normalized_selected_namespaces,
|
|
887
|
+
retention_count=payload.retention_count,
|
|
886
888
|
)
|
|
887
889
|
|
|
888
890
|
folder_directory = _normalize_folder_backup_path(folder_path=folder_path)
|
|
@@ -895,7 +897,7 @@ def run_backup(token: Annotated[str, Depends(_require_auth)]):
|
|
|
895
897
|
database_path = resolve_namespaced_database_path(namespace=namespace)
|
|
896
898
|
backup_info = create_timestamped_backup_for_paths(database_path, folder_directory)
|
|
897
899
|
current_folder_backups = list_backups_in_directory(folder_directory, database_path=database_path)
|
|
898
|
-
folder_delete_count = len(current_folder_backups) - retention_count
|
|
900
|
+
folder_delete_count = len(current_folder_backups) - payload.retention_count
|
|
899
901
|
deleted_folder_count = 0
|
|
900
902
|
if folder_delete_count > 0:
|
|
901
903
|
deleted_folder_count = len(
|
|
@@ -71,6 +71,7 @@ from app.services.undo_state import reset_undo_stack
|
|
|
71
71
|
from app.usecases.prioritize import list_prioritize_tag_suggestions
|
|
72
72
|
from app.services.selected_text_tag import SelectedTextTagValidationError
|
|
73
73
|
from app.api.request_auth import require_request_auth_token
|
|
74
|
+
from app.security.shell_execution import is_loopback_host
|
|
74
75
|
|
|
75
76
|
|
|
76
77
|
logger = logging.getLogger(__name__)
|
|
@@ -93,6 +94,22 @@ def _require_note_present(note_id: str, *, context: str) -> None:
|
|
|
93
94
|
raise HTTPException(status_code=404, detail=f"Note not found: {note_id}")
|
|
94
95
|
|
|
95
96
|
|
|
97
|
+
def _require_loopback_shell_request(request: Request) -> None:
|
|
98
|
+
if not isinstance(request, Request):
|
|
99
|
+
raise TypeError("request must be a Request")
|
|
100
|
+
if request.client is None or not is_loopback_host(host=request.client.host):
|
|
101
|
+
raise HTTPException(
|
|
102
|
+
status_code=403,
|
|
103
|
+
detail="Shell execution is restricted to loopback clients",
|
|
104
|
+
)
|
|
105
|
+
request_hostname = request.url.hostname
|
|
106
|
+
if request_hostname is None or not is_loopback_host(host=request_hostname):
|
|
107
|
+
raise HTTPException(
|
|
108
|
+
status_code=403,
|
|
109
|
+
detail="Shell execution requires a loopback request host",
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
96
113
|
def _resolve_tab_sort_mode(tab_id: object) -> str:
|
|
97
114
|
if tab_id is not None and (not isinstance(tab_id, str) or tab_id == ""):
|
|
98
115
|
raise TypeError("tabId must be a non-empty string")
|
|
@@ -883,7 +900,8 @@ def resize_note_image(request: Request, note_id: str, body: dict):
|
|
|
883
900
|
|
|
884
901
|
@router.post("/notes/{note_id}/run-shell")
|
|
885
902
|
@transactional_route
|
|
886
|
-
def run_shell_endpoint(note_id: str, body: dict) -> Dict[str, object]:
|
|
903
|
+
def run_shell_endpoint(request: Request, note_id: str, body: dict) -> Dict[str, object]:
|
|
904
|
+
_require_loopback_shell_request(request)
|
|
887
905
|
_require_note_present(note_id, context="notes.run-shell")
|
|
888
906
|
timeout_seconds = body["timeoutSeconds"]
|
|
889
907
|
cmd = CmdRunShellStart(
|
|
@@ -894,7 +912,8 @@ def run_shell_endpoint(note_id: str, body: dict) -> Dict[str, object]:
|
|
|
894
912
|
|
|
895
913
|
|
|
896
914
|
@router.get("/notes/{note_id}/run-shell/{run_id}")
|
|
897
|
-
def run_shell_status_endpoint(note_id: str, run_id: str) -> Dict[str, object]:
|
|
915
|
+
def run_shell_status_endpoint(request: Request, note_id: str, run_id: str) -> Dict[str, object]:
|
|
916
|
+
_require_loopback_shell_request(request)
|
|
898
917
|
run_capture = CapturedExceptionContext(RuntimeError, TypeError, ValueError)
|
|
899
918
|
result: Dict[str, object] | None = None
|
|
900
919
|
with run_capture:
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter, HTTPException, Request, Response
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
|
|
6
|
+
from app.api.request_auth import require_request_auth_token
|
|
7
|
+
from app.api.transactions import transactional_route
|
|
8
|
+
from app.services.exception_capture import CapturedExceptionContext
|
|
9
|
+
from app.services.remote_image_proxy import (
|
|
10
|
+
RemoteImageFetchError,
|
|
11
|
+
fetch_remote_image,
|
|
12
|
+
remote_image_proxy_registry,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
router = APIRouter(prefix="/remote-images", tags=["remote-images"])
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class RemoteImageRegistrationRequest(BaseModel):
|
|
20
|
+
source_urls: list[str] = Field(min_length=1, max_length=100)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class RemoteImageRegistrationEntryResponse(BaseModel):
|
|
24
|
+
source_url: str
|
|
25
|
+
proxy_path: str
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class RemoteImageRegistrationResponse(BaseModel):
|
|
29
|
+
images: list[RemoteImageRegistrationEntryResponse]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@router.post("/registrations", response_model=RemoteImageRegistrationResponse)
|
|
33
|
+
@transactional_route
|
|
34
|
+
def register_remote_images(
|
|
35
|
+
request: Request,
|
|
36
|
+
registration: RemoteImageRegistrationRequest,
|
|
37
|
+
) -> RemoteImageRegistrationResponse:
|
|
38
|
+
require_request_auth_token(request)
|
|
39
|
+
images: list[RemoteImageRegistrationEntryResponse] = []
|
|
40
|
+
for source_url in registration.source_urls:
|
|
41
|
+
registration_capture = CapturedExceptionContext(ValueError)
|
|
42
|
+
token = None
|
|
43
|
+
with registration_capture:
|
|
44
|
+
token = remote_image_proxy_registry.register(source_url)
|
|
45
|
+
if registration_capture.captured_exception is not None:
|
|
46
|
+
raise HTTPException(
|
|
47
|
+
status_code=422,
|
|
48
|
+
detail="Remote image URL must use HTTP or HTTPS",
|
|
49
|
+
)
|
|
50
|
+
if token is None:
|
|
51
|
+
raise RuntimeError("Remote image registration completed without a token")
|
|
52
|
+
images.append(
|
|
53
|
+
RemoteImageRegistrationEntryResponse(
|
|
54
|
+
source_url=source_url,
|
|
55
|
+
proxy_path=f"/api2/remote-images/{token}",
|
|
56
|
+
)
|
|
57
|
+
)
|
|
58
|
+
return RemoteImageRegistrationResponse(images=images)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@router.get("/{proxy_token}")
|
|
62
|
+
def proxy_remote_image(request: Request, proxy_token: str) -> Response:
|
|
63
|
+
require_request_auth_token(request)
|
|
64
|
+
resolve_capture = CapturedExceptionContext(KeyError)
|
|
65
|
+
source_url = None
|
|
66
|
+
with resolve_capture:
|
|
67
|
+
source_url = remote_image_proxy_registry.resolve(proxy_token)
|
|
68
|
+
if resolve_capture.captured_exception is not None:
|
|
69
|
+
raise HTTPException(status_code=404, detail="Remote image reference not found")
|
|
70
|
+
if source_url is None:
|
|
71
|
+
raise RuntimeError("Remote image proxy resolution completed without a source URL")
|
|
72
|
+
|
|
73
|
+
fetch_capture = CapturedExceptionContext(RemoteImageFetchError)
|
|
74
|
+
payload = None
|
|
75
|
+
with fetch_capture:
|
|
76
|
+
payload = fetch_remote_image(source_url)
|
|
77
|
+
if fetch_capture.captured_exception is not None:
|
|
78
|
+
raise HTTPException(status_code=502, detail="Remote image unavailable")
|
|
79
|
+
if payload is None:
|
|
80
|
+
raise RuntimeError("Remote image fetch completed without a payload")
|
|
81
|
+
return Response(
|
|
82
|
+
content=payload.content,
|
|
83
|
+
media_type=payload.mime_type,
|
|
84
|
+
headers={
|
|
85
|
+
"Cache-Control": "no-store, private",
|
|
86
|
+
"Cross-Origin-Resource-Policy": "same-origin",
|
|
87
|
+
},
|
|
88
|
+
)
|
|
@@ -69,6 +69,9 @@ KDF_MIN_PARALLELISM = 1
|
|
|
69
69
|
KDF_MAX_PARALLELISM = 16
|
|
70
70
|
KDF_ALGORITHM = "ARGON2ID"
|
|
71
71
|
VAULT_VERSION = 3
|
|
72
|
+
PASSWORD_MIN_LENGTH = 12
|
|
73
|
+
PASSWORD_MAX_LENGTH = 72
|
|
74
|
+
PASSWORD_MIN_ZXCVBN_SCORE = 3
|
|
72
75
|
|
|
73
76
|
# Login brute-force mitigation
|
|
74
77
|
LOGIN_RATE_LIMIT_MAX_ATTEMPTS = _env_int("LOGIN_RATE_LIMIT_MAX_ATTEMPTS", 5)
|
|
@@ -83,6 +86,8 @@ SECURITY_REQUIRE_MACOS_NO_HIBERNATION = _env_flag("SECURITY_REQUIRE_MACOS_NO_HIB
|
|
|
83
86
|
assert KDF_MIN_TIME_COST <= KDF_TIME_COST <= KDF_MAX_TIME_COST
|
|
84
87
|
assert KDF_MIN_MEMORY_COST_KIB <= KDF_MEMORY_COST_KIB <= KDF_MAX_MEMORY_COST_KIB
|
|
85
88
|
assert KDF_MIN_PARALLELISM <= KDF_PARALLELISM <= KDF_MAX_PARALLELISM
|
|
89
|
+
assert 0 < PASSWORD_MIN_LENGTH <= PASSWORD_MAX_LENGTH
|
|
90
|
+
assert 0 <= PASSWORD_MIN_ZXCVBN_SCORE <= 4
|
|
86
91
|
assert MAX_SEARCH_SUGGESTIONS > 0
|
|
87
92
|
assert MAX_TAG_SUGGESTIONS > 0
|
|
88
93
|
assert LOGIN_RATE_LIMIT_MAX_ATTEMPTS > 0
|
|
@@ -108,9 +108,9 @@ class FileSession:
|
|
|
108
108
|
extra={"stack": stack},
|
|
109
109
|
)
|
|
110
110
|
else:
|
|
111
|
-
logger.
|
|
111
|
+
logger.error(
|
|
112
112
|
"FATAL: file DB rollback executed; crashing process",
|
|
113
|
-
extra={"stack": stack},
|
|
113
|
+
extra={"stack": stack, "error_type": exc_type.__name__},
|
|
114
114
|
)
|
|
115
115
|
sys.stderr.write("FATAL: file DB rollback executed; crashing process\n")
|
|
116
116
|
sys.stderr.flush()
|
|
@@ -6,12 +6,11 @@ from collections.abc import Callable
|
|
|
6
6
|
from dataclasses import dataclass
|
|
7
7
|
import sqlite3
|
|
8
8
|
|
|
9
|
+
from app.db.version import CURRENT_DATABASE_VERSION
|
|
10
|
+
from app.db.schema import create_namespace_content_migrations_table
|
|
9
11
|
from app.services.encryption import EncryptionService
|
|
10
12
|
|
|
11
13
|
|
|
12
|
-
CURRENT_DATABASE_VERSION = 1
|
|
13
|
-
|
|
14
|
-
|
|
15
14
|
@dataclass(frozen=True, slots=True)
|
|
16
15
|
class MigrationResult:
|
|
17
16
|
initial_version: int
|
|
@@ -124,11 +123,22 @@ def _migration_0_to_1(
|
|
|
124
123
|
return rewritten_count
|
|
125
124
|
|
|
126
125
|
|
|
126
|
+
def _migration_1_to_2(
|
|
127
|
+
*,
|
|
128
|
+
connection: sqlite3.Connection,
|
|
129
|
+
encryption_enabled: bool, # noqa: ARG001
|
|
130
|
+
encryption_service: EncryptionService | None, # noqa: ARG001
|
|
131
|
+
) -> int:
|
|
132
|
+
create_namespace_content_migrations_table(connection)
|
|
133
|
+
return 0
|
|
134
|
+
|
|
135
|
+
|
|
127
136
|
_MIGRATIONS: dict[
|
|
128
137
|
int,
|
|
129
138
|
Callable[..., int],
|
|
130
139
|
] = {
|
|
131
140
|
0: _migration_0_to_1,
|
|
141
|
+
1: _migration_1_to_2,
|
|
132
142
|
}
|
|
133
143
|
|
|
134
144
|
|