aird 0.4.23.dev20__tar.gz → 0.4.23.dev22__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.
Files changed (220) hide show
  1. {aird-0.4.23.dev20/aird.egg-info → aird-0.4.23.dev22}/PKG-INFO +2 -1
  2. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/cli/main.py +100 -60
  3. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/cli/session.py +100 -67
  4. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/config.py +83 -58
  5. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/constants/__init__.py +9 -3
  6. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/core/file_operations.py +58 -41
  7. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/core/folder_size.py +24 -42
  8. aird-0.4.23.dev22/aird/core/http_range.py +114 -0
  9. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/core/mmap_handler.py +49 -31
  10. aird-0.4.23.dev22/aird/core/secret_storage.py +72 -0
  11. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/core/security.py +27 -2
  12. aird-0.4.23.dev22/aird/core/webauthn_config.py +20 -0
  13. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/core/zip_download.py +50 -36
  14. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/database/db.py +7 -0
  15. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/db/network_shares.py +6 -3
  16. aird-0.4.23.dev22/aird/db/ranged_uploads.py +73 -0
  17. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/db/schema.py +35 -0
  18. aird-0.4.23.dev22/aird/db/webauthn.py +248 -0
  19. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/handlers/admin_handlers.py +1 -0
  20. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/handlers/api_handlers.py +66 -45
  21. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/handlers/auth_handlers.py +55 -8
  22. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/handlers/base_handler.py +45 -20
  23. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/handlers/constants.py +6 -0
  24. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/handlers/file_op_handlers.py +21 -1
  25. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/handlers/p2p_handlers.py +12 -5
  26. aird-0.4.23.dev22/aird/handlers/ranged_upload_handlers.py +264 -0
  27. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/handlers/share_handlers.py +41 -25
  28. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/handlers/transfer_ws_handlers.py +6 -3
  29. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/handlers/view_handlers.py +594 -541
  30. aird-0.4.23.dev22/aird/handlers/webauthn_handlers.py +393 -0
  31. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/main.py +22 -0
  32. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/services/policy_service.py +1 -2
  33. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/static/css/app.css +1 -1
  34. aird-0.4.23.dev22/aird/static/js/aird-core.js +476 -0
  35. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/static/js/browse/app.js +398 -370
  36. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/static/js/download-manager.js +51 -43
  37. aird-0.4.23.dev22/aird/static/js/file-transfer-http.js +387 -0
  38. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/static/js/file-transfer-ws.js +349 -301
  39. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/static/js/folder-size-scan.js +7 -4
  40. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/static/js/pages/p2p-page.js +2 -2
  41. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/static/js/share/app.js +69 -48
  42. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/static/js/transfer-tracker.js +63 -35
  43. aird-0.4.23.dev22/aird/static/js/webauthn.js +259 -0
  44. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/templates/_app_nav_header.html +17 -14
  45. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/templates/_bg_canvas.html +1 -1
  46. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/templates/_theme_early.html +5 -5
  47. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/templates/_theme_login_corner.html +26 -14
  48. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/templates/admin.html +4 -0
  49. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/templates/admin_audit.html +1 -0
  50. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/templates/admin_login.html +2 -1
  51. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/templates/admin_tags.html +2 -2
  52. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/templates/browse.html +39 -11
  53. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/templates/edit.html +2 -1
  54. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/templates/file.html +2 -2
  55. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/templates/login.html +68 -5
  56. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/templates/p2p_transfer.html +2 -1
  57. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/templates/profile.html +70 -0
  58. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/templates/share.html +3 -2
  59. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/templates/tagged_files.html +1 -1
  60. {aird-0.4.23.dev20 → aird-0.4.23.dev22/aird.egg-info}/PKG-INFO +2 -1
  61. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird.egg-info/SOURCES.txt +12 -0
  62. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird.egg-info/requires.txt +1 -0
  63. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/setup.py +2 -1
  64. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_architecture_conformance.py +4 -1
  65. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_auth_handlers_extended.py +4 -0
  66. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_core_file_operations.py +250 -231
  67. aird-0.4.23.dev22/tests/test_http_range.py +41 -0
  68. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_multi_user.py +2 -1
  69. aird-0.4.23.dev22/tests/test_secret_storage.py +27 -0
  70. aird-0.4.23.dev22/tests/test_webauthn_handlers.py +137 -0
  71. aird-0.4.23.dev20/aird/static/js/aird-core.js +0 -227
  72. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/LICENSE +0 -0
  73. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/MANIFEST.in +0 -0
  74. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/README.md +0 -0
  75. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/__init__.py +0 -0
  76. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/__main__.py +0 -0
  77. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/app_context.py +0 -0
  78. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/cli/__init__.py +0 -0
  79. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/cli/__main__.py +0 -0
  80. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/cli/authelia.py +0 -0
  81. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/cli/config.py +0 -0
  82. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/cloud/__init__.py +0 -0
  83. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/constants/admin.py +0 -0
  84. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/constants/file_ops.py +0 -0
  85. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/constants/input_limits.py +0 -0
  86. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/constants/media.py +0 -0
  87. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/core/__init__.py +0 -0
  88. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/core/events.py +0 -0
  89. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/core/filter_expression.py +0 -0
  90. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/core/input_validation.py +0 -0
  91. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/core/share_root.py +0 -0
  92. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/core/websocket_manager.py +0 -0
  93. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/database/__init__.py +0 -0
  94. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/database/feature_flags.py +0 -0
  95. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/database/ldap.py +0 -0
  96. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/db/__init__.py +0 -0
  97. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/db/audit.py +0 -0
  98. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/db/config.py +0 -0
  99. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/db/favorites.py +0 -0
  100. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/db/policies.py +0 -0
  101. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/db/policy_decisions.py +0 -0
  102. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/db/policy_seeds.py +0 -0
  103. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/db/quota.py +0 -0
  104. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/db/resource_tags.py +0 -0
  105. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/db/shares.py +0 -0
  106. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/db/user_attributes.py +0 -0
  107. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/db/users.py +0 -0
  108. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/domain/__init__.py +0 -0
  109. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/domain/contracts.py +0 -0
  110. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/domain/models.py +0 -0
  111. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/email/__init__.py +0 -0
  112. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/email/brevo.py +0 -0
  113. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/email/resolve.py +0 -0
  114. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/event_loop.py +0 -0
  115. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/handlers/__init__.py +0 -0
  116. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/handlers/abac_handlers.py +0 -0
  117. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/handlers/health_handler.py +0 -0
  118. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/network_share_manager.py +0 -0
  119. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/server_runtime.py +0 -0
  120. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/services/__init__.py +0 -0
  121. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/services/audit_service.py +0 -0
  122. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/services/config_service.py +0 -0
  123. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/services/email_service.py +0 -0
  124. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/services/email_subscriber.py +0 -0
  125. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/services/event_subscribers.py +0 -0
  126. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/services/favorites_service.py +0 -0
  127. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/services/network_share_service.py +0 -0
  128. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/services/p2p_service.py +0 -0
  129. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/services/quota_service.py +0 -0
  130. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/services/share_service.py +0 -0
  131. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/services/tag_service.py +0 -0
  132. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/services/user_service.py +0 -0
  133. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/sql_identifiers.py +0 -0
  134. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/static/favicon.png +0 -0
  135. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/static/favicon.svg +0 -0
  136. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/static/img/logo-icon.png +0 -0
  137. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/static/img/logo-mark.svg +0 -0
  138. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/static/img/logo-text.png +0 -0
  139. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/static/img/logo.png +0 -0
  140. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/static/js/bg-canvas.js +0 -0
  141. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/static/js/common/command-palette.js +0 -0
  142. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/static/js/components/folder-picker.js +0 -0
  143. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/static/js/feature-flags-live.js +0 -0
  144. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/static/js/login-ui.js +0 -0
  145. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/static/js/media-view.js +0 -0
  146. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/static/js/p2p/app.js +0 -0
  147. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/static/js/p2p/mediator.js +0 -0
  148. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/static/js/p2p/qr-adapter.js +0 -0
  149. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/static/js/p2p/signaling-service.js +0 -0
  150. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/static/js/p2p/state-machine.js +0 -0
  151. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/static/js/p2p/transfer-service.js +0 -0
  152. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/static/js/pages/super-search.js +0 -0
  153. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/static/js/theme.js +0 -0
  154. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/static/js/vendor/pdf.min.js +0 -0
  155. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/static/js/vendor/pdf.worker.min.js +0 -0
  156. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/static/js/vendor/qrcode-browser.js +0 -0
  157. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/templates/_admin_tabs.html +0 -0
  158. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/templates/admin_ldap.html +0 -0
  159. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/templates/admin_network_shares.html +0 -0
  160. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/templates/admin_policies.html +0 -0
  161. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/templates/admin_user_attributes.html +0 -0
  162. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/templates/admin_users.html +0 -0
  163. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/templates/directory.html +0 -0
  164. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/templates/error.html +0 -0
  165. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/templates/ldap_config_create.html +0 -0
  166. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/templates/ldap_config_edit.html +0 -0
  167. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/templates/media_view.html +0 -0
  168. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/templates/shared_list.html +0 -0
  169. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/templates/super_search.html +0 -0
  170. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/templates/token_verification.html +0 -0
  171. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/templates/user_create.html +0 -0
  172. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/templates/user_edit.html +0 -0
  173. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/utils/__init__.py +0 -0
  174. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird/utils/util.py +0 -0
  175. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird.egg-info/dependency_links.txt +0 -0
  176. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird.egg-info/entry_points.txt +0 -0
  177. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/aird.egg-info/top_level.txt +0 -0
  178. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/setup.cfg +0 -0
  179. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/__init__.py +0 -0
  180. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/conftest.py +0 -0
  181. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/handler_helpers.py +0 -0
  182. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_admin_handlers.py +0 -0
  183. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_api_handlers.py +0 -0
  184. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_auth_handlers.py +0 -0
  185. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_base_handler.py +0 -0
  186. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_base_handler_pep.py +0 -0
  187. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_cli.py +0 -0
  188. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_cloud.py +0 -0
  189. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_config.py +0 -0
  190. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_database_db.py +0 -0
  191. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_database_feature_flags.py +0 -0
  192. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_database_ldap.py +0 -0
  193. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_database_shares.py +0 -0
  194. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_database_users.py +0 -0
  195. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_database_users_hashing.py +0 -0
  196. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_db.py +0 -0
  197. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_email_service.py +0 -0
  198. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_file_op_handlers.py +0 -0
  199. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_filter_expression.py +0 -0
  200. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_folder_size.py +0 -0
  201. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_main.py +0 -0
  202. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_mmap_handler.py +0 -0
  203. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_network_shares.py +0 -0
  204. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_p2p_handlers.py +0 -0
  205. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_password_hashing.py +0 -0
  206. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_policy_service.py +0 -0
  207. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_rate_limit.py +0 -0
  208. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_security.py +0 -0
  209. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_security_comprehensive.py +0 -0
  210. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_server_runtime.py +0 -0
  211. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_share_handlers.py +0 -0
  212. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_share_ownership.py +0 -0
  213. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_super_search_handler.py +0 -0
  214. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_tag_service.py +0 -0
  215. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_transfer_ws_handlers.py +0 -0
  216. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_util.py +0 -0
  217. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_view_handlers.py +0 -0
  218. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_websocket_manager.py +0 -0
  219. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_wheel_static_assets.py +0 -0
  220. {aird-0.4.23.dev20 → aird-0.4.23.dev22}/tests/test_zip_download.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aird
3
- Version: 0.4.23.dev20
3
+ Version: 0.4.23.dev22
4
4
  Summary: Aird - A lightweight web-based file browser, editor, and streamer with real-time capabilities
5
5
  Home-page: https://github.com/blinkerbit/aird
6
6
  Author: Viswantha Srinivas P
@@ -23,6 +23,7 @@ Requires-Dist: pysmbserver>=0.1.0; python_version >= "3.13"
23
23
  Requires-Dist: wsgidav>=4.3.0
24
24
  Requires-Dist: cheroot>=10.0.0
25
25
  Requires-Dist: pyasn1>=0.6.2
26
+ Requires-Dist: webauthn>=2.0.0
26
27
  Dynamic: author
27
28
  Dynamic: author-email
28
29
  Dynamic: classifier
@@ -42,27 +42,92 @@ def cmd_config_show(_args: argparse.Namespace) -> int:
42
42
  cfg = load_config()
43
43
  if not cfg:
44
44
  print("(no config — use: aird-cli config set server https://host)")
45
- return 0
46
- for k, v in sorted(cfg.items()):
47
- if k == "password":
48
- print(f"{k}: ***")
49
- else:
50
- print(f"{k}: {v}")
45
+ else:
46
+ for k, v in sorted(cfg.items()):
47
+ if k == "password":
48
+ print(f"{k}: ***")
49
+ else:
50
+ print(f"{k}: {v}")
51
51
  return 0
52
52
 
53
53
 
54
- def cmd_login(args: argparse.Namespace) -> int:
54
+ def _resolve_login_server(args: argparse.Namespace) -> str | None:
55
55
  server = get_server_url()
56
56
  if not server and args.server:
57
57
  cfg = load_config()
58
58
  cfg["server"] = args.server.rstrip("/")
59
59
  save_config(cfg)
60
60
  server = cfg["server"]
61
+ return server
62
+
63
+
64
+ def _try_token_login(client: AirdClient, server: str, args: argparse.Namespace) -> int | None:
65
+ if not args.token:
66
+ return None
67
+ client.set_bearer_token(args.token)
68
+ client.refresh_xsrf()
69
+ client.save()
70
+ try:
71
+ client.check_auth()
72
+ print(f"Logged in to {server} (access token)")
73
+ return 0
74
+ except (AirdAuthError, AirdAPIError) as exc:
75
+ print(f"Token login failed: {exc}", file=sys.stderr)
76
+ return 1
77
+
78
+
79
+ def _run_authelia_login(
80
+ client: AirdClient,
81
+ authelia_url: str,
82
+ username: str,
83
+ password: str,
84
+ totp: str | None,
85
+ server: str,
86
+ ) -> int | None:
87
+ try:
88
+ authelia_login(
89
+ client.http,
90
+ authelia_url,
91
+ username,
92
+ password,
93
+ totp=totp,
94
+ target_url=f"{server}/login",
95
+ )
96
+ except AutheliaError as exc:
97
+ if str(exc) != "second_factor_required":
98
+ print(f"Authelia login failed: {exc}", file=sys.stderr)
99
+ return 1
100
+ totp = totp or _prompt_totp()
101
+ try:
102
+ second_factor(client.http, authelia_url, totp)
103
+ except AutheliaError as exc2:
104
+ print(f"Authelia login failed: {exc2}", file=sys.stderr)
105
+ return 1
106
+ return None
107
+
108
+
109
+ def _save_login_config(
110
+ username: str, server: str, authelia_url: str | None
111
+ ) -> None:
112
+ cfg = load_config()
113
+ cfg["username"] = username
114
+ cfg["server"] = server
115
+ if authelia_url:
116
+ cfg["authelia_url"] = authelia_url
117
+ save_config(cfg)
118
+
119
+
120
+ def cmd_login(args: argparse.Namespace) -> int:
121
+ server = _resolve_login_server(args)
61
122
  if not server:
62
123
  print("Set server URL first: aird-cli config set server https://your-host", file=sys.stderr)
63
124
  return 1
64
125
 
65
126
  client = AirdClient(server)
127
+ token_result = _try_token_login(client, server, args)
128
+ if token_result is not None:
129
+ return token_result
130
+
66
131
  authelia_url = get_authelia_url() or args.authelia_url
67
132
 
68
133
  username = args.username or load_config().get("username") or input("Username: ").strip()
@@ -70,55 +135,22 @@ def cmd_login(args: argparse.Namespace) -> int:
70
135
  print("Username required", file=sys.stderr)
71
136
  return 1
72
137
 
73
- if args.token:
74
- client.set_bearer_token(args.token)
75
- client.refresh_xsrf()
76
- client.save()
77
- try:
78
- client.check_auth()
79
- print(f"Logged in to {server} (access token)")
80
- return 0
81
- except (AirdAuthError, AirdAPIError) as exc:
82
- print(f"Token login failed: {exc}", file=sys.stderr)
83
- return 1
84
-
85
138
  password = args.password or _prompt_password()
86
139
  totp = args.totp
87
140
 
88
141
  if authelia_url:
89
- try:
90
- authelia_login(
91
- client.http,
92
- authelia_url,
93
- username,
94
- password,
95
- totp=totp,
96
- target_url=f"{server}/login",
97
- )
98
- except AutheliaError as exc:
99
- if str(exc) == "second_factor_required":
100
- totp = totp or _prompt_totp()
101
- try:
102
- second_factor(client.http, authelia_url, totp)
103
- except AutheliaError as exc2:
104
- print(f"Authelia login failed: {exc2}", file=sys.stderr)
105
- return 1
106
- else:
107
- print(f"Authelia login failed: {exc}", file=sys.stderr)
108
- return 1
142
+ authelia_result = _run_authelia_login(
143
+ client, authelia_url, username, password, totp, server
144
+ )
145
+ if authelia_result is not None:
146
+ return authelia_result
109
147
 
110
- aird_token = args.aird_token
111
- if aird_token:
112
- client.login_password("", "", token=aird_token)
148
+ if args.aird_token:
149
+ client.login_password("", "", token=args.aird_token)
113
150
  else:
114
151
  client.login_password(username, password)
115
152
 
116
- cfg = load_config()
117
- cfg["username"] = username
118
- cfg["server"] = server
119
- if authelia_url:
120
- cfg["authelia_url"] = authelia_url
121
- save_config(cfg)
153
+ _save_login_config(username, server, authelia_url)
122
154
  client.save()
123
155
 
124
156
  try:
@@ -250,6 +282,24 @@ def cmd_upload(args: argparse.Namespace) -> int:
250
282
  return 1
251
283
 
252
284
 
285
+ def _print_my_shares(mine) -> None:
286
+ print("My shares:")
287
+ items = mine.values() if isinstance(mine, dict) else mine
288
+ for s in items:
289
+ sid = s.get("id", "?")
290
+ paths = s.get("paths") or []
291
+ print(f" {sid}\t{len(paths)} path(s)\t{s.get('created_at', '')}")
292
+
293
+
294
+ def _print_shared_with_me(shared) -> None:
295
+ print("Shared with me:")
296
+ for s in shared:
297
+ sid = s.get("id", "?")
298
+ creator = s.get("creator") or s.get("username") or "?"
299
+ paths = s.get("paths") or []
300
+ print(f" {sid}\tfrom {creator}\t{len(paths)} path(s)")
301
+
302
+
253
303
  def cmd_shares_list(_args: argparse.Namespace) -> int:
254
304
  try:
255
305
  client = AirdClient()
@@ -260,19 +310,9 @@ def cmd_shares_list(_args: argparse.Namespace) -> int:
260
310
  print("(no shares)")
261
311
  return 0
262
312
  if mine:
263
- print("My shares:")
264
- items = mine.values() if isinstance(mine, dict) else mine
265
- for s in items:
266
- sid = s.get("id", "?")
267
- paths = s.get("paths") or []
268
- print(f" {sid}\t{len(paths)} path(s)\t{s.get('created_at', '')}")
313
+ _print_my_shares(mine)
269
314
  if shared:
270
- print("Shared with me:")
271
- for s in shared:
272
- sid = s.get("id", "?")
273
- creator = s.get("creator") or s.get("username") or "?"
274
- paths = s.get("paths") or []
275
- print(f" {sid}\tfrom {creator}\t{len(paths)} path(s)")
315
+ _print_shared_with_me(shared)
276
316
  return 0
277
317
  except (AirdAuthError, AirdAPIError) as exc:
278
318
  print(str(exc), file=sys.stderr)
@@ -91,6 +91,43 @@ def _load_cookies(session: requests.Session) -> bool:
91
91
  return bool(data.get("cookies") or token)
92
92
 
93
93
 
94
+ def _collect_local_upload_files(
95
+ local_dir: Path, remote_dir: str
96
+ ) -> list[tuple[Path, str]]:
97
+ files: list[tuple[Path, str]] = []
98
+
99
+ def walk(base: Path, rel_remote: str) -> None:
100
+ for child in sorted(base.iterdir()):
101
+ rel = f"{rel_remote}/{child.name}" if rel_remote else child.name
102
+ if child.is_dir():
103
+ walk(child, rel)
104
+ elif child.is_file():
105
+ remote_parent = (
106
+ f"{remote_dir}/{rel_remote}".strip("/") if rel_remote else remote_dir
107
+ )
108
+ files.append((child, remote_parent.strip("/")))
109
+
110
+ walk(local_dir, "")
111
+ return files
112
+
113
+
114
+ def _run_path_jobs(
115
+ file_paths: list[str],
116
+ job: Callable[[str], str],
117
+ workers: int,
118
+ on_progress: Callable[[str], None] | None,
119
+ ) -> int:
120
+ count = 0
121
+ with ThreadPoolExecutor(max_workers=max(1, workers)) as pool:
122
+ futures = [pool.submit(job, p) for p in file_paths]
123
+ for fut in as_completed(futures):
124
+ p = fut.result()
125
+ count += 1
126
+ if on_progress:
127
+ on_progress(p)
128
+ return count
129
+
130
+
94
131
  class AirdClient:
95
132
  def __init__(self, server_url: str | None = None, *, _reuse: "AirdClient | None" = None):
96
133
  if _reuse is not None:
@@ -309,29 +346,15 @@ class AirdClient:
309
346
  ) -> int:
310
347
  if not local_dir.is_dir():
311
348
  raise NotADirectoryError(local_dir)
312
- local_dir = local_dir.resolve()
313
- files: list[tuple[Path, str]] = []
314
-
315
- def walk(base: Path, rel_remote: str) -> None:
316
- for child in sorted(base.iterdir()):
317
- rel = f"{rel_remote}/{child.name}" if rel_remote else child.name
318
- if child.is_dir():
319
- walk(child, rel)
320
- elif child.is_file():
321
- remote_parent = (
322
- f"{remote_dir}/{rel_remote}".strip("/") if rel_remote else remote_dir
323
- )
324
- files.append((child, remote_parent.strip("/")))
325
-
326
- walk(local_dir, "")
349
+ files = _collect_local_upload_files(local_dir.resolve(), remote_dir)
327
350
  if not files:
328
351
  return 0
329
352
 
330
- count = 0
331
353
  def _upload_job(lp: Path, rd: str) -> Path:
332
354
  self.clone().upload_file(lp, rd)
333
355
  return lp
334
356
 
357
+ count = 0
335
358
  with ThreadPoolExecutor(max_workers=max(1, workers)) as pool:
336
359
  futures = {pool.submit(_upload_job, lp, rd): (lp, rd) for lp, rd in files}
337
360
  for fut in as_completed(futures):
@@ -360,31 +383,34 @@ class AirdClient:
360
383
  return s, False
361
384
  return None, False
362
385
 
386
+ def _expand_single_path(self, raw: str, seen: set[str], files: list[str]) -> None:
387
+ p = (raw or "").strip("/")
388
+ if not p:
389
+ return
390
+ try:
391
+ self.list_dir(p)
392
+ for rel, _ in self.iter_tree(p):
393
+ if rel not in seen:
394
+ seen.add(rel)
395
+ files.append(rel)
396
+ except AirdAPIError as exc:
397
+ if exc.status == 404:
398
+ if p not in seen:
399
+ seen.add(p)
400
+ files.append(p)
401
+ else:
402
+ raise
403
+
363
404
  def expand_paths_to_files(self, paths: list[str]) -> list[str]:
364
405
  """Expand share path entries (files or folders) to file paths in your tree."""
365
406
  files: list[str] = []
366
407
  seen: set[str] = set()
367
408
  for raw in paths:
368
- p = (raw or "").strip("/")
369
- if not p:
370
- continue
371
- try:
372
- self.list_dir(p)
373
- for rel, _ in self.iter_tree(p):
374
- if rel not in seen:
375
- seen.add(rel)
376
- files.append(rel)
377
- except AirdAPIError as exc:
378
- if exc.status == 404:
379
- if p not in seen:
380
- seen.add(p)
381
- files.append(p)
382
- else:
383
- raise
409
+ self._expand_single_path(raw, seen, files)
384
410
  return files
385
411
 
386
412
  def verify_share(self, share_id: str, token: str) -> None:
387
- xsrf = self.refresh_xsrf()
413
+ self.refresh_xsrf()
388
414
  r = self.http.post(
389
415
  self._url(f"/shared/{quote(share_id)}/verify"),
390
416
  json={"token": token},
@@ -433,6 +459,42 @@ class AirdClient:
433
459
  if chunk:
434
460
  f.write(chunk)
435
461
 
462
+ def _own_share_download_job(self, local_dir: Path) -> Callable[[str], str]:
463
+ def _own_job(path: str) -> str:
464
+ dest = local_dir / path
465
+ self.clone().download_file(path, dest)
466
+ return path
467
+
468
+ return _own_job
469
+
470
+ def _shared_download_job(
471
+ self, share_id: str, local_dir: Path
472
+ ) -> Callable[[str], str]:
473
+ def _shared_job(path: str) -> str:
474
+ dest = local_dir / path
475
+ self.clone().download_share_file(share_id, path, dest)
476
+ return path
477
+
478
+ return _shared_job
479
+
480
+ def _resolve_own_share_paths(self, share: dict) -> list[str]:
481
+ paths = self.expand_paths_to_files(list(share.get("paths") or []))
482
+ if not paths:
483
+ raise AirdAPIError("No files in share")
484
+ return paths
485
+
486
+ def _resolve_shared_file_paths(
487
+ self, share_id: str, share_token: str | None, share: dict | None
488
+ ) -> list[str]:
489
+ if share_token:
490
+ self.verify_share(share_id, share_token)
491
+ file_paths = self.share_file_paths(share_id)
492
+ if not file_paths and share:
493
+ file_paths = list(share.get("paths") or [])
494
+ if not file_paths:
495
+ raise AirdAPIError("No files in share (or access denied — try --token)")
496
+ return file_paths
497
+
436
498
  def download_share(
437
499
  self,
438
500
  share_id: str,
@@ -445,42 +507,13 @@ class AirdClient:
445
507
  share, is_mine = self.find_share(share_id)
446
508
 
447
509
  if is_mine and share:
448
- paths = self.expand_paths_to_files(list(share.get("paths") or []))
449
- if not paths:
450
- raise AirdAPIError("No files in share")
451
-
452
- def _own_job(path: str) -> str:
453
- dest = local_dir / path
454
- self.clone().download_file(path, dest)
455
- return path
456
-
457
- jobs = _own_job
458
- file_paths = paths
510
+ file_paths = self._resolve_own_share_paths(share)
511
+ jobs = self._own_share_download_job(local_dir)
459
512
  else:
460
- if share_token:
461
- self.verify_share(share_id, share_token)
462
- file_paths = self.share_file_paths(share_id)
463
- if not file_paths and share:
464
- file_paths = list(share.get("paths") or [])
465
- if not file_paths:
466
- raise AirdAPIError("No files in share (or access denied — try --token)")
467
-
468
- def _shared_job(path: str) -> str:
469
- dest = local_dir / path
470
- self.clone().download_share_file(share_id, path, dest)
471
- return path
472
-
473
- jobs = _shared_job
513
+ file_paths = self._resolve_shared_file_paths(share_id, share_token, share)
514
+ jobs = self._shared_download_job(share_id, local_dir)
474
515
 
475
- count = 0
476
- with ThreadPoolExecutor(max_workers=max(1, workers)) as pool:
477
- futures = [pool.submit(jobs, p) for p in file_paths]
478
- for fut in as_completed(futures):
479
- p = fut.result()
480
- count += 1
481
- if on_progress:
482
- on_progress(p)
483
- return count
516
+ return _run_path_jobs(file_paths, jobs, workers, on_progress)
484
517
 
485
518
  def download_all_shares(
486
519
  self,
@@ -40,7 +40,7 @@ FEATURE_FLAGS = {}
40
40
  CLOUD_MANAGER = CloudManager()
41
41
  WEBSOCKET_CONFIG = {}
42
42
  MULTI_USER = False
43
- WORKERS = None # None = auto (1.25 * threads_per_core * physical_cores)
43
+ WORKERS = None
44
44
  DB_CONN = None
45
45
  MAX_FILE_SIZE = _MAX_FILE_SIZE
46
46
  MAX_READABLE_FILE_SIZE = _MAX_READABLE_FILE_SIZE
@@ -187,6 +187,82 @@ def _apply_feature_flags_from_config(config: dict) -> None:
187
187
  FEATURE_FLAGS[feature_name] = bool(feature_value)
188
188
 
189
189
 
190
+ def _load_config_dict(args) -> dict:
191
+ global CONFIG_FILE
192
+ if not args.config:
193
+ return {}
194
+ CONFIG_FILE = args.config
195
+ with open(CONFIG_FILE, encoding="utf-8") as f:
196
+ return json.load(f)
197
+
198
+
199
+ def _apply_access_tokens(args, config: dict) -> tuple[bool, bool]:
200
+ """Apply ACCESS_TOKEN and ADMIN_TOKEN; return (access_explicit, admin_explicit)."""
201
+ global ACCESS_TOKEN, ADMIN_TOKEN
202
+
203
+ token_provided_explicitly = bool(
204
+ args.token or config.get("token") or os.environ.get("AIRD_ACCESS_TOKEN")
205
+ )
206
+ admin_token_provided_explicitly = bool(
207
+ args.admin_token or config.get("admin_token")
208
+ )
209
+
210
+ ACCESS_TOKEN = (
211
+ args.token
212
+ or config.get("token")
213
+ or os.environ.get("AIRD_ACCESS_TOKEN")
214
+ or secrets.token_urlsafe(64)
215
+ )
216
+ ADMIN_TOKEN = (
217
+ args.admin_token or config.get("admin_token") or secrets.token_urlsafe(64)
218
+ )
219
+ return token_provided_explicitly, admin_token_provided_explicitly
220
+
221
+
222
+ def _apply_server_settings(args, config: dict) -> None:
223
+ global ROOT_DIR, PORT, MULTI_USER, WORKERS, SSL_CERT, SSL_KEY, ADMIN_USERS, HOSTNAME
224
+
225
+ ROOT_DIR = args.root or config.get("root") or os.getcwd()
226
+ PORT = args.port or config.get("port") or 8000
227
+ MULTI_USER = args.multi_user or config.get("multi_user", False)
228
+ workers_arg = args.workers if args.workers is not None else config.get("workers")
229
+ WORKERS = int(workers_arg) if workers_arg is not None else None
230
+ SSL_CERT = args.ssl_cert or config.get("ssl_cert")
231
+ SSL_KEY = args.ssl_key or config.get("ssl_key")
232
+ ADMIN_USERS = config.get("admin_users", [])
233
+ HOSTNAME = args.hostname or config.get("hostname") or socket.getfqdn()
234
+
235
+
236
+ def _apply_ldap_globals(ldap_settings: dict) -> None:
237
+ global LDAP_ENABLED, LDAP_SERVER, LDAP_BASE_DN, LDAP_USER_TEMPLATE
238
+ global LDAP_FILTER_TEMPLATE, LDAP_ATTRIBUTES, LDAP_ATTRIBUTE_MAP
239
+
240
+ LDAP_ENABLED = ldap_settings["enabled"]
241
+ LDAP_SERVER = ldap_settings["server"]
242
+ LDAP_BASE_DN = ldap_settings["base_dn"]
243
+ LDAP_USER_TEMPLATE = ldap_settings["user_template"]
244
+ LDAP_FILTER_TEMPLATE = ldap_settings["filter_template"]
245
+ LDAP_ATTRIBUTES = ldap_settings["attributes"]
246
+ LDAP_ATTRIBUTE_MAP = ldap_settings["attribute_map"]
247
+
248
+
249
+ def _print_generated_tokens(
250
+ token_provided_explicitly: bool, admin_token_provided_explicitly: bool
251
+ ) -> None:
252
+ if not token_provided_explicitly:
253
+ print(f"\n{'='*60}")
254
+ print(f"Access token (generated): {ACCESS_TOKEN}")
255
+ print(f"{'='*60}")
256
+ print("Note: Copy the token above exactly as shown .")
257
+ print("WARNING: Store this token securely. It grants access to your files.")
258
+ print(f"{'='*60}\n")
259
+ if not admin_token_provided_explicitly:
260
+ print(f"\n{'='*60}")
261
+ print(f"Admin token (generated): {ADMIN_TOKEN}")
262
+ print("WARNING: Store this token securely. It grants admin access.")
263
+ print(f"{'='*60}\n")
264
+
265
+
190
266
  def init_config():
191
267
  """
192
268
  Initializes the application configuration by parsing command-line arguments,
@@ -236,70 +312,19 @@ def init_config():
236
312
  )
237
313
  args = parser.parse_args()
238
314
 
239
- config = {}
240
- if args.config:
241
- CONFIG_FILE = args.config
242
- with open(CONFIG_FILE) as f:
243
- config = json.load(f)
244
- else:
245
- config = {}
315
+ config = _load_config_dict(args)
246
316
 
247
317
  _configure_cloud_providers(config)
248
318
 
249
- ROOT_DIR = args.root or config.get("root") or os.getcwd()
250
- PORT = args.port or config.get("port") or 8000
251
-
252
- token_provided_explicitly = bool(
253
- args.token or config.get("token") or os.environ.get("AIRD_ACCESS_TOKEN")
254
- )
255
- admin_token_provided_explicitly = bool(
256
- args.admin_token or config.get("admin_token")
257
- )
258
-
259
- ACCESS_TOKEN = (
260
- args.token
261
- or config.get("token")
262
- or os.environ.get("AIRD_ACCESS_TOKEN")
263
- or secrets.token_urlsafe(64)
264
- )
265
- ADMIN_TOKEN = (
266
- args.admin_token or config.get("admin_token") or secrets.token_urlsafe(64)
319
+ token_provided_explicitly, admin_token_provided_explicitly = _apply_access_tokens(
320
+ args, config
267
321
  )
322
+ _apply_server_settings(args, config)
268
323
 
269
324
  ldap_settings = _parse_ldap_settings(args, config)
270
- LDAP_ENABLED = ldap_settings["enabled"]
271
- LDAP_SERVER = ldap_settings["server"]
272
- LDAP_BASE_DN = ldap_settings["base_dn"]
273
- LDAP_USER_TEMPLATE = ldap_settings["user_template"]
274
- LDAP_FILTER_TEMPLATE = ldap_settings["filter_template"]
275
- LDAP_ATTRIBUTES = ldap_settings["attributes"]
276
- LDAP_ATTRIBUTE_MAP = ldap_settings["attribute_map"]
325
+ _apply_ldap_globals(ldap_settings)
277
326
 
278
327
  _apply_feature_flags_from_config(config)
279
328
  _apply_brevo_settings(config)
280
329
 
281
- MULTI_USER = args.multi_user or config.get("multi_user", False)
282
-
283
- workers_arg = args.workers if args.workers is not None else config.get("workers")
284
- WORKERS = int(workers_arg) if workers_arg is not None else None
285
-
286
- SSL_CERT = args.ssl_cert or config.get("ssl_cert")
287
- SSL_KEY = args.ssl_key or config.get("ssl_key")
288
-
289
- ADMIN_USERS = config.get("admin_users", [])
290
-
291
- HOSTNAME = args.hostname or config.get("hostname") or socket.getfqdn()
292
-
293
- # Print tokens when they were not explicitly provided (masked for security)
294
- if not token_provided_explicitly:
295
- print(f"\n{'='*60}")
296
- print(f"Access token (generated): {ACCESS_TOKEN}")
297
- print(f"{'='*60}")
298
- print("Note: Copy the token above exactly as shown .")
299
- print("WARNING: Store this token securely. It grants access to your files.")
300
- print(f"{'='*60}\n")
301
- if not admin_token_provided_explicitly:
302
- print(f"\n{'='*60}")
303
- print(f"Admin token (generated): {ADMIN_TOKEN}")
304
- print("WARNING: Store this token securely. It grants admin access.")
305
- print(f"{'='*60}\n")
330
+ _print_generated_tokens(token_provided_explicitly, admin_token_provided_explicitly)
@@ -43,6 +43,7 @@ FEATURE_FLAGS = {
43
43
  "abac_engine": False,
44
44
  "abac_audit_decisions": True,
45
45
  "email_notifications": False,
46
+ "webauthn": False,
46
47
  }
47
48
 
48
49
  # WebSocket connection configuration
@@ -63,8 +64,13 @@ UPLOAD_CONFIG = {
63
64
 
64
65
  # File operation constants (derived from UPLOAD_CONFIG at startup)
65
66
  MAX_FILE_SIZE = UPLOAD_CONFIG["max_file_size_mb"] * 1024 * 1024
66
- # HTTP /upload body limit (browser uploads use WebSocket; CLI may POST whole file)
67
+ # HTTP /upload body limit (browser small uploads + CLI)
67
68
  UPLOAD_REQUEST_MAX_BODY_SIZE = MAX_FILE_SIZE + (1024 * 1024)
69
+ # Files below this use single POST /upload; at or above use Content-Range API
70
+ LARGE_FILE_THRESHOLD_BYTES = 500 * 1024 * 1024
71
+ RANGE_CHUNK_BYTES = 16 * 1024 * 1024
72
+ # Max JSON WebSocket control message size (search, stream commands, P2P signaling)
73
+ WS_JSON_MESSAGE_MAX_BYTES = 64 * 1024
68
74
  MAX_READABLE_FILE_SIZE = 50 * 1024 * 1024 # 50 MB
69
75
 
70
76
  # Default line window for /files/... viewer when no ?end_line= is supplied (protects DOM from huge renders)
@@ -101,8 +107,8 @@ UPLOAD_ALLOWED_EXTENSIONS = set(ALLOWED_UPLOAD_EXTENSIONS)
101
107
  # Mmap constants
102
108
  MMAP_MIN_SIZE = 1 * 1024 * 1024 # 1 MB
103
109
  CHUNK_SIZE = 64 * 1024 # 64 KB
104
- # WebSocket binary frame size (stay under Cloudflare ~1 MiB message limit)
105
- WS_TRANSFER_FRAME_BYTES = 768 * 1024
110
+ # Legacy WebSocket frame size (optional fallback; HTTP is primary for transfers)
111
+ WS_TRANSFER_FRAME_BYTES = 2 * 1024 * 1024
106
112
 
107
113
  # Network share manager (set at startup)
108
114
  NETWORK_SHARE_MANAGER = None