django-microsys 2.2.4__tar.gz → 2.2.5__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.
- {django_microsys-2.2.4 → django_microsys-2.2.5}/PKG-INFO +1 -1
- {django_microsys-2.2.4 → django_microsys-2.2.5}/django_microsys.egg-info/PKG-INFO +1 -1
- {django_microsys-2.2.4 → django_microsys-2.2.5}/django_microsys.egg-info/SOURCES.txt +2 -0
- django_microsys-2.2.5/microsys/VERSION +1 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/forms.py +25 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/management/commands/microsys_settings.py +1 -0
- django_microsys-2.2.5/microsys/migrations/0007_systemsettings_prevent_multiple_active_sessions.py +18 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/models.py +3 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/main/js/system_setup.js +1 -1
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/users/js/profile_2fa.js +46 -3
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/users/profile.html +25 -5
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/translations.py +20 -0
- django_microsys-2.2.5/microsys/trust.py +227 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/urls.py +1 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/utils.py +11 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/views/__init__.py +1 -1
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/views/profile.py +54 -10
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/views/twofa.py +17 -102
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/views/users.py +2 -0
- django_microsys-2.2.4/microsys/VERSION +0 -1
- {django_microsys-2.2.4 → django_microsys-2.2.5}/LICENSE +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/MANIFEST.in +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/README.md +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/django_microsys.egg-info/dependency_links.txt +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/django_microsys.egg-info/entry_points.txt +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/django_microsys.egg-info/requires.txt +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/django_microsys.egg-info/top_level.txt +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/__init__.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/__main__.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/admin.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/api.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/apps.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/cli.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/constants.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/context_processors.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/discovery.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/fetcher.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/filters.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/fonts.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/formats/__init__.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/formats/ar/__init__.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/formats/ar/formats.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/formats/en/__init__.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/formats/en/formats.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/guards.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/management/commands/__init__.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/management/commands/microsys_check.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/management/commands/microsys_setup.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/management/commands/migrator.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/management/commands/seed_activity_log.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/managers.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/middleware.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/migrations/0001_initial.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/migrations/0002_public_registration.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/migrations/0003_public_root_split.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/migrations/0004_client_ip_and_trusted_devices.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/migrations/0005_systemsettings_allow_user_font_override_and_more.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/migrations/0006_systemsettings_navbar_config.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/migrations/__init__.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/navbar.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/patches.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/registration.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/app/README.md.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/app/__init__.py.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/app/apps.py.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/app/filters.py.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/app/forms.py.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/app/migrations/__init__.py.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/app/models.py.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/app/tables.py.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/app/templates/example_record_confirm_delete.html.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/app/templates/example_record_detail.html.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/app/templates/example_record_form.html.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/app/templates/example_record_list.html.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/app/tests/__init__.py.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/app/tests/test_app.py.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/app/translations.py.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/app/urls.py.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/app/views.py.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/project/.nginx/nginx.conf.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/project/.secrets/.env.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/project/Dockerfile.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/project/README.md.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/project/compose.dev.yml.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/project/compose.yml.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/project/dockerignore.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/project/docs/README.md.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/project/entrypoint.sh.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/project/gitattributes.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/project/gitignore.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/project/gunicorn.py.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/project/manage.py.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/project/package/__init__.py.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/project/package/asgi.py.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/project/package/celery.py.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/project/package/settings.py.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/project/package/urls.py.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/project/package/wsgi.py.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/project/req.txt.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/project/start.ps1.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/project/start.sh.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/project/tests/__init__.py.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/project/tests/test_scaffold.py.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/scaffold_templates/project/tools/smtp_relay.py.tmpl +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/signals.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/bootstrap/bootstrap-icons.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/bootstrap/bootstrap-icons.woff +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/bootstrap/bootstrap-icons.woff2 +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/bootstrap/bootstrap.bundle.min.js +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/bootstrap/bootstrap.bundle.min.js.map +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/bootstrap/bootstrap.min.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/bootstrap/bootstrap.min.css.map +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/bootstrap/bootstrap.rtl.min.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/bootstrap/bootstrap.rtl.min.css.map +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/favicon.ico +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/img/base_logo.webp +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/img/default_profile.webp +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/img/login_logo.webp +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/accessibility/css/main.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/accessibility/js/main.js +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/activitylog/js/main.js +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/fonts/TwemojiCountryFlags.woff2 +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/fonts/cairo-bold.woff2 +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/fonts/cairo-medium.woff2 +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/fonts/cairo-regular.woff2 +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/fonts/shabwa-bold.woff2 +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/fonts/shabwa-medium.woff2 +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/fonts/shabwa-regular.woff2 +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/forms/css/file_field.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/forms/css/form_actions.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/forms/css/form_fields.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/forms/js/file_field.js +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/forms/js/filter_form.js +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/helpers/autofill/js/main.js +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/helpers/context_menu/css/main.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/helpers/context_menu/js/main.js +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/helpers/context_menu/js/section_manager.js +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/helpers/dynamic_modal/js/main.js +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/helpers/prevent_double_submit.js +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/helpers/scan_link/js/main.js +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/helpers/scan_link/js/scan_button.js +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/helpers/wizard/js/main.js +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/language/css/main.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/language/js/main.js +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/language/js/translations.js +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/main/css/buttons.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/main/css/dropdowns.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/main/css/index_cards.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/main/css/main.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/main/css/navbar.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/main/css/options.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/main/css/pagination.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/main/css/selectors.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/main/css/system_setup.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/main/css/tables.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/main/css/template_cleanup.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/main/css/titlebar.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/main/js/base_head.js +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/main/js/base_runtime.js +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/main/js/navbar.js +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/main/js/options.js +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/main/js/selectors.js +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/main/js/tables.js +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/sections/js/manage_sections.js +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/sidebar/css/main.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/sidebar/css/reorder.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/sidebar/css/theme_picker.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/sidebar/js/main.js +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/sidebar/js/preload.js +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/sidebar/js/reorder.js +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/sidebar/js/theme_picker.js +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/themes/css/blue.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/themes/css/dark.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/themes/css/gold.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/themes/css/gothic.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/themes/css/green.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/themes/css/light.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/themes/css/mono.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/themes/css/neon.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/themes/css/red.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/themes/css/retro.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/themes/js/main.js +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/tutorial/css/main.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/tutorial/js/driver.js +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/tutorial/js/main.js +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/users/css/login.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/users/css/permissions.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/users/css/profile.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/users/css/user_hub.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/users/js/login.js +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/users/js/manage_users.js +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/users/js/permissions.js +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/users/js/profile_image_widget.js +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/users/js/twofa_verify.js +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/users/js/user_hub.js +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/vanillajs-datepicker/datepicker-bs5.min.css +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/vanillajs-datepicker/datepicker.min.js +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/tables.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/bootstrap5/layout/field_file.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/2fa/verify.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/activitylog/activity_log.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/activitylog/activity_log_detail_modal.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/base.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/form_base.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/forms/assets_head.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/forms/assets_scripts.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/forms/crispy_file_field.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/forms/file_input.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/forms/filter_assets_head.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/forms/filter_assets_scripts.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/helpers/dynamic_modal.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/helpers/dynamic_modal_combined.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/helpers/dynamic_modal_detail.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/helpers/dynamic_modal_form.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/helpers/dynamic_modal_list.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/helpers/micro_context_menu.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/includes/font_previews.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/includes/font_settings_matrix.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/includes/language_catalog_editor.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/includes/language_fonts_editor.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/includes/language_previews.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/includes/navbar.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/includes/navbar_builder.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/includes/navbar_mode_previews.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/includes/options.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/includes/sidebar_builder.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/includes/sidebar_density_previews.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/includes/sidebar_items.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/includes/system_names_editor.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/includes/system_setup.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/includes/table_density_previews.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/includes/theme_previews.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/includes/theme_settings_matrix.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/includes/titlebar.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/includes/translation_matrix_editor.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/includes/tutorial.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/list_base.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/registration/pending.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/scopes/scope_actions.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/scopes/scope_form.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/scopes/scope_manager.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/sections/manage_sections.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/sections/subsection_select.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/sidebar/auto.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/sidebar/extra_groups.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/sidebar/main.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/sidebar/tree.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/tables/table.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/users/grouped_permissions.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/users/manage_users.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/users/profile_image_widget.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/users/user_detail_modal.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/users/user_hub.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/registration/email/verify_registration.txt +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/registration/login.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/registration/register.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/registration/register_sent.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/registration/register_verify.html +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templatetags/__init__.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templatetags/microsys_tags.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templatetags/microsys_translation.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templatetags/sidebar_tags.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/themes.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/views/activitylog.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/views/general.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/views/registration.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/views/scopes.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/views/sections.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/views/sidebar.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/widgets.py +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/pyproject.toml +0 -0
- {django_microsys-2.2.4 → django_microsys-2.2.5}/setup.cfg +0 -0
|
@@ -34,6 +34,7 @@ microsys/signals.py
|
|
|
34
34
|
microsys/tables.py
|
|
35
35
|
microsys/themes.py
|
|
36
36
|
microsys/translations.py
|
|
37
|
+
microsys/trust.py
|
|
37
38
|
microsys/urls.py
|
|
38
39
|
microsys/utils.py
|
|
39
40
|
microsys/widgets.py
|
|
@@ -54,6 +55,7 @@ microsys/migrations/0003_public_root_split.py
|
|
|
54
55
|
microsys/migrations/0004_client_ip_and_trusted_devices.py
|
|
55
56
|
microsys/migrations/0005_systemsettings_allow_user_font_override_and_more.py
|
|
56
57
|
microsys/migrations/0006_systemsettings_navbar_config.py
|
|
58
|
+
microsys/migrations/0007_systemsettings_prevent_multiple_active_sessions.py
|
|
57
59
|
microsys/migrations/__init__.py
|
|
58
60
|
microsys/scaffold_templates/app/README.md.tmpl
|
|
59
61
|
microsys/scaffold_templates/app/__init__.py.tmpl
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.2.5
|
|
@@ -1616,6 +1616,10 @@ class SystemSettingsForm(forms.ModelForm):
|
|
|
1616
1616
|
required=False,
|
|
1617
1617
|
initial=False,
|
|
1618
1618
|
)
|
|
1619
|
+
prevent_multiple_active_sessions = forms.BooleanField(
|
|
1620
|
+
required=False,
|
|
1621
|
+
initial=False,
|
|
1622
|
+
)
|
|
1619
1623
|
client_ip_config = forms.CharField(
|
|
1620
1624
|
widget=forms.HiddenInput(),
|
|
1621
1625
|
required=False,
|
|
@@ -1675,6 +1679,7 @@ class SystemSettingsForm(forms.ModelForm):
|
|
|
1675
1679
|
'allow_user_language_override',
|
|
1676
1680
|
'default_table_density',
|
|
1677
1681
|
'email_2fa',
|
|
1682
|
+
'prevent_multiple_active_sessions',
|
|
1678
1683
|
'client_ip_config',
|
|
1679
1684
|
'public_root',
|
|
1680
1685
|
'public_root_split_enabled',
|
|
@@ -1955,6 +1960,8 @@ class SystemSettingsForm(forms.ModelForm):
|
|
|
1955
1960
|
'help_sys_email_2fa',
|
|
1956
1961
|
'Allow users to enable two-factor authentication via email. Requires Microsys email delivery to be ready.',
|
|
1957
1962
|
)
|
|
1963
|
+
self.fields['prevent_multiple_active_sessions'].label = s.get('form_sys_prevent_multiple_active_sessions')
|
|
1964
|
+
self.fields['prevent_multiple_active_sessions'].help_text = s.get('help_sys_prevent_multiple_active_sessions')
|
|
1958
1965
|
self.fields['client_ip_mode'].label = s.get('form_sys_client_ip_mode')
|
|
1959
1966
|
self.fields['client_ip_mode'].help_text = s.get('help_sys_client_ip_mode')
|
|
1960
1967
|
self.fields['client_ip_mode'].choices = (
|
|
@@ -2326,6 +2333,10 @@ class SystemSettingsForm(forms.ModelForm):
|
|
|
2326
2333
|
getattr(self.instance, 'email_2fa', False)
|
|
2327
2334
|
or config.get('email_2fa', False)
|
|
2328
2335
|
)
|
|
2336
|
+
self.initial['prevent_multiple_active_sessions'] = bool(
|
|
2337
|
+
getattr(self.instance, 'prevent_multiple_active_sessions', False)
|
|
2338
|
+
or config.get('prevent_multiple_active_sessions', False)
|
|
2339
|
+
)
|
|
2329
2340
|
initial_client_ip_config = normalize_client_ip_config(
|
|
2330
2341
|
(
|
|
2331
2342
|
getattr(self.instance, 'client_ip_config', None)
|
|
@@ -2721,6 +2732,7 @@ class SystemSettingsForm(forms.ModelForm):
|
|
|
2721
2732
|
Row(
|
|
2722
2733
|
build_settings_toggle_field(self, 'public_root', css_class='col-lg-6'),
|
|
2723
2734
|
build_settings_toggle_field(self, 'email_2fa', css_class='col-lg-6'),
|
|
2735
|
+
build_settings_toggle_field(self, 'prevent_multiple_active_sessions', css_class='col-lg-12'),
|
|
2724
2736
|
css_class='g-3 mb-3',
|
|
2725
2737
|
),
|
|
2726
2738
|
HTML(f"<h6 class='fw-bold my-3'>{s.get('client_ip_settings_title')}</h6>"),
|
|
@@ -3071,6 +3083,17 @@ class SystemSettingsForm(forms.ModelForm):
|
|
|
3071
3083
|
raise ValidationError("Invalid table density choice.")
|
|
3072
3084
|
return value
|
|
3073
3085
|
|
|
3086
|
+
def clean_prevent_multiple_active_sessions(self):
|
|
3087
|
+
if (
|
|
3088
|
+
self.is_bound
|
|
3089
|
+
and self.mode != 'setup'
|
|
3090
|
+
and self.single_step_mode
|
|
3091
|
+
and self.single_step_index != 2
|
|
3092
|
+
and 'prevent_multiple_active_sessions' not in self.data
|
|
3093
|
+
):
|
|
3094
|
+
return bool(getattr(self.instance, 'prevent_multiple_active_sessions', False))
|
|
3095
|
+
return bool(self.cleaned_data.get('prevent_multiple_active_sessions', False))
|
|
3096
|
+
|
|
3074
3097
|
def clean_sidebar_density(self):
|
|
3075
3098
|
value = self.cleaned_data.get('sidebar_density') or DEFAULT_SIDEBAR_DENSITY
|
|
3076
3099
|
if value not in SIDEBAR_DENSITY_VALUES:
|
|
@@ -3247,6 +3270,7 @@ class SystemSettingsForm(forms.ModelForm):
|
|
|
3247
3270
|
'allow_user_language_override',
|
|
3248
3271
|
'default_table_density',
|
|
3249
3272
|
'email_2fa',
|
|
3273
|
+
'prevent_multiple_active_sessions',
|
|
3250
3274
|
'client_ip_config',
|
|
3251
3275
|
'public_root',
|
|
3252
3276
|
'public_root_split_enabled',
|
|
@@ -3495,6 +3519,7 @@ class SystemSettingsForm(forms.ModelForm):
|
|
|
3495
3519
|
'allow_user_language_override': bool(self.cleaned_data.get('allow_user_language_override', True)),
|
|
3496
3520
|
'default_table_density': self.cleaned_data.get('default_table_density', DEFAULT_TABLE_DENSITY),
|
|
3497
3521
|
'email_2fa': bool(self.cleaned_data.get('email_2fa', False)),
|
|
3522
|
+
'prevent_multiple_active_sessions': bool(self.cleaned_data.get('prevent_multiple_active_sessions', False)),
|
|
3498
3523
|
'client_ip_config': self.cleaned_data.get('client_ip_config', default_client_ip_config()),
|
|
3499
3524
|
'public_root': bool(self.cleaned_data.get('public_root', False)),
|
|
3500
3525
|
'public_root_split_enabled': bool(self.cleaned_data.get('public_root_split_enabled', False)),
|
{django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/management/commands/microsys_settings.py
RENAMED
|
@@ -61,6 +61,7 @@ class Command(BaseCommand):
|
|
|
61
61
|
self.stdout.write(f"home_url: {instance.home_url or ''}")
|
|
62
62
|
self.stdout.write(f"default_language: {instance.default_language or ''}")
|
|
63
63
|
self.stdout.write(f"default_theme: {instance.default_theme or ''}")
|
|
64
|
+
self.stdout.write(f"prevent_multiple_active_sessions: {bool(getattr(instance, 'prevent_multiple_active_sessions', False))}")
|
|
64
65
|
self.stdout.write(f"sidebar_enabled: {bool((instance.sidebar_config or {}).get('enabled', True))}")
|
|
65
66
|
self.stdout.write(f"navbar_enabled: {bool((instance.navbar_config or {}).get('enabled', False))}")
|
|
66
67
|
|
django_microsys-2.2.5/microsys/migrations/0007_systemsettings_prevent_multiple_active_sessions.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Generated by Django on 2026-05-24
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
('microsys', '0006_systemsettings_navbar_config'),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.AddField(
|
|
14
|
+
model_name='systemsettings',
|
|
15
|
+
name='prevent_multiple_active_sessions',
|
|
16
|
+
field=models.BooleanField(default=False, verbose_name='Prevent Multiple Active Sessions'),
|
|
17
|
+
),
|
|
18
|
+
]
|
|
@@ -148,6 +148,8 @@ class SingletonModel(models.Model):
|
|
|
148
148
|
obj.navbar_config = config.get('navbar')
|
|
149
149
|
if hasattr(obj, 'email_2fa') and 'email_2fa' in config:
|
|
150
150
|
obj.email_2fa = bool(config.get('email_2fa'))
|
|
151
|
+
if hasattr(obj, 'prevent_multiple_active_sessions') and 'prevent_multiple_active_sessions' in config:
|
|
152
|
+
obj.prevent_multiple_active_sessions = bool(config.get('prevent_multiple_active_sessions'))
|
|
151
153
|
if hasattr(obj, 'public_root') and 'public_root' in config:
|
|
152
154
|
obj.public_root = bool(config.get('public_root'))
|
|
153
155
|
if hasattr(obj, 'public_root_split_enabled') and 'public_root_split_enabled' in config:
|
|
@@ -213,6 +215,7 @@ class SystemSettings(SingletonModel):
|
|
|
213
215
|
sidebar_config = models.JSONField(default=dict, blank=True, verbose_name="Sidebar Configuration")
|
|
214
216
|
navbar_config = models.JSONField(default=default_navbar_config, blank=True, verbose_name="Nav Bar Configuration")
|
|
215
217
|
titlebar_config = models.JSONField(default=default_titlebar_config, blank=True, verbose_name="Titlebar Configuration")
|
|
218
|
+
prevent_multiple_active_sessions = models.BooleanField(default=False, verbose_name="Prevent Multiple Active Sessions")
|
|
216
219
|
|
|
217
220
|
class Meta:
|
|
218
221
|
verbose_name = "System Settings"
|
{django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/main/js/system_setup.js
RENAMED
|
@@ -3209,7 +3209,7 @@
|
|
|
3209
3209
|
}
|
|
3210
3210
|
});
|
|
3211
3211
|
|
|
3212
|
-
['allow_user_theme_override', 'allow_user_font_override', 'allow_user_language_override', 'email_2fa', 'public_root', 'public_root_split_enabled', 'public_registration_enabled', 'registration_throttle_enabled'].forEach((name) => {
|
|
3212
|
+
['allow_user_theme_override', 'allow_user_font_override', 'allow_user_language_override', 'email_2fa', 'prevent_multiple_active_sessions', 'public_root', 'public_root_split_enabled', 'public_registration_enabled', 'registration_throttle_enabled'].forEach((name) => {
|
|
3213
3213
|
if (Object.prototype.hasOwnProperty.call(settings, name)) {
|
|
3214
3214
|
setCheckboxField(form, name, settings[name]);
|
|
3215
3215
|
}
|
{django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/static/microsys/users/js/profile_2fa.js
RENAMED
|
@@ -29,9 +29,17 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
|
29
29
|
|
|
30
30
|
document.body.addEventListener('submit', function(e) {
|
|
31
31
|
const revokeForm = e.target.closest('.profile-session-revoke-form');
|
|
32
|
-
if (
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
if (revokeForm) {
|
|
33
|
+
e.preventDefault();
|
|
34
|
+
confirmSessionRevoke(revokeForm);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const trustForm = e.target.closest('.profile-session-trust-form');
|
|
39
|
+
if (trustForm) {
|
|
40
|
+
e.preventDefault();
|
|
41
|
+
confirmSessionTrust(trustForm);
|
|
42
|
+
}
|
|
35
43
|
});
|
|
36
44
|
|
|
37
45
|
if (otpSetupForm) {
|
|
@@ -631,6 +639,41 @@ function confirmSessionRevoke(form) {
|
|
|
631
639
|
});
|
|
632
640
|
}
|
|
633
641
|
|
|
642
|
+
function confirmSessionTrust(form) {
|
|
643
|
+
const confirmMsg = form.dataset.confirmMsg || '';
|
|
644
|
+
const csrfToken = form.querySelector('[name=csrfmiddlewaretoken]')?.value || '';
|
|
645
|
+
const submitButton = form.querySelector('button[type="submit"]');
|
|
646
|
+
|
|
647
|
+
showConfirmation({
|
|
648
|
+
message: confirmMsg,
|
|
649
|
+
requirePassword: true,
|
|
650
|
+
onConfirm: function(currentPassword) {
|
|
651
|
+
const body = new URLSearchParams(new FormData(form));
|
|
652
|
+
body.set('current_password', currentPassword);
|
|
653
|
+
setButtonLoading(submitButton, true);
|
|
654
|
+
|
|
655
|
+
return fetch(form.action, {
|
|
656
|
+
method: 'POST',
|
|
657
|
+
headers: {
|
|
658
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
659
|
+
'X-CSRFToken': csrfToken,
|
|
660
|
+
'X-Requested-With': 'XMLHttpRequest'
|
|
661
|
+
},
|
|
662
|
+
body: body.toString()
|
|
663
|
+
})
|
|
664
|
+
.then(parseJsonResponse)
|
|
665
|
+
.then(data => {
|
|
666
|
+
if (data.status !== 'success') {
|
|
667
|
+
throw new Error(data.message || 'Request failed.');
|
|
668
|
+
}
|
|
669
|
+
window.location.assign(data.redirect_url || window.location.href);
|
|
670
|
+
return true;
|
|
671
|
+
})
|
|
672
|
+
.finally(() => setButtonLoading(submitButton, false));
|
|
673
|
+
}
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
|
|
634
677
|
function downloadBackupCodes() {
|
|
635
678
|
const container = document.getElementById('backupCodesContainer');
|
|
636
679
|
// Get text content effectively
|
{django_microsys-2.2.4 → django_microsys-2.2.5}/microsys/templates/microsys/users/profile.html
RENAMED
|
@@ -305,10 +305,23 @@
|
|
|
305
305
|
</div>
|
|
306
306
|
<div class="profile-session-action">
|
|
307
307
|
{% if session.is_current %}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
308
|
+
{% if session.is_trusted %}
|
|
309
|
+
<button type="button" class="btn btn-sm btn-outline-secondary rounded-pill" disabled>
|
|
310
|
+
{{ MS_TRANS.current }}
|
|
311
|
+
</button>
|
|
312
|
+
{% else %}
|
|
313
|
+
<form method="post"
|
|
314
|
+
action="{% url 'trust_current_device' %}"
|
|
315
|
+
class="profile-session-trust-form"
|
|
316
|
+
data-confirm-msg="{{ MS_TRANS.msg_confirm_trust_current_device }}">
|
|
317
|
+
{% csrf_token %}
|
|
318
|
+
<input type="hidden" name="current_password" value="">
|
|
319
|
+
<button type="submit" class="btn btn-sm btn-outline-primary rounded-pill">
|
|
320
|
+
<i class="bi bi-shield-plus me-1"></i>{{ MS_TRANS.trust_this_device }}
|
|
321
|
+
</button>
|
|
322
|
+
</form>
|
|
323
|
+
{% endif %}
|
|
324
|
+
{% elif session.can_revoke %}
|
|
312
325
|
<form method="post"
|
|
313
326
|
action="{% url 'revoke_profile_session' session.session_key %}"
|
|
314
327
|
class="profile-session-revoke-form"
|
|
@@ -319,6 +332,13 @@
|
|
|
319
332
|
<i class="bi bi-box-arrow-right me-1"></i>{{ MS_TRANS.sign_out }}
|
|
320
333
|
</button>
|
|
321
334
|
</form>
|
|
335
|
+
{% else %}
|
|
336
|
+
<button type="button"
|
|
337
|
+
class="btn btn-sm btn-outline-secondary rounded-pill"
|
|
338
|
+
data-ms-tooltip="{{ MS_TRANS.session_revoke_trusted_denied }}"
|
|
339
|
+
disabled>
|
|
340
|
+
<i class="bi bi-shield-lock me-1"></i>{{ MS_TRANS.protected }}
|
|
341
|
+
</button>
|
|
322
342
|
{% endif %}
|
|
323
343
|
</div>
|
|
324
344
|
</div>
|
|
@@ -635,5 +655,5 @@
|
|
|
635
655
|
{% endblock %}
|
|
636
656
|
|
|
637
657
|
{% block scripts %}
|
|
638
|
-
<script src="{% static 'microsys/users/js/profile_2fa.js' %}?v=
|
|
658
|
+
<script src="{% static 'microsys/users/js/profile_2fa.js' %}?v=20260524a" nonce="{{ request.csp_nonce }}"></script>
|
|
639
659
|
{% endblock %}
|
|
@@ -253,6 +253,8 @@ MICROSYS_STRINGS = {
|
|
|
253
253
|
'titlebar_surface_glass_desc': 'سطح زجاجي مع ضبابية خفيفة.',
|
|
254
254
|
'form_sys_email_2fa': 'تفعيل التحقق الثنائي عبر البريد الإلكتروني',
|
|
255
255
|
'help_sys_email_2fa': 'السماح للمستخدمين بتفعيل التحقق الثنائي عبر البريد الإلكتروني. يتطلب جاهزية إعدادات توصيل البريد في Microsys.',
|
|
256
|
+
'form_sys_prevent_multiple_active_sessions': 'منع تعدد الجلسات النشطة',
|
|
257
|
+
'help_sys_prevent_multiple_active_sessions': 'عند التفعيل، تصبح الجلسة الموثوقة الجديدة هي الجلسة النشطة الوحيدة لهذا المستخدم. تسجيل الدخول غير الموثوق يبقى مسموحاً لكنه لا يطرد الجلسات الموثوقة.',
|
|
256
258
|
'form_sys_client_ip_mode': 'مصدر عنوان IP للعميل',
|
|
257
259
|
'help_sys_client_ip_mode': 'اختر رأس الطلب الذي يجب أن يثق به Microsys عند تسجيل عناوين IP الخاصة بتسجيل الدخول والجلسات والأمان.',
|
|
258
260
|
'client_ip_mode_x_forwarded_for': 'X-Forwarded-For',
|
|
@@ -662,8 +664,15 @@ MICROSYS_STRINGS = {
|
|
|
662
664
|
'current_session': 'الجلسة الحالية',
|
|
663
665
|
'trusted_device_badge': 'جهاز موثوق',
|
|
664
666
|
'trusted_until': 'موثوق حتى',
|
|
667
|
+
'trust_this_device': 'ثق بهذا الجهاز',
|
|
668
|
+
'trusted_device_added_success': 'تم اعتبار هذا الجهاز موثوقاً.',
|
|
669
|
+
'session_revoke_trusted_denied': 'لا يمكن لجلسة غير موثوقة إنهاء جلسة موثوقة.',
|
|
670
|
+
'session_revoked_success': 'تم إنهاء الجلسة.',
|
|
671
|
+
'session_revoke_denied': 'هذه الجلسة لا تنتمي إلى حسابك.',
|
|
665
672
|
'session_expires': 'تنتهي في',
|
|
666
673
|
'current': 'الحالي',
|
|
674
|
+
'protected': 'محمي',
|
|
675
|
+
'sign_out': 'تسجيل الخروج',
|
|
667
676
|
'no_active_sessions': 'لم يتم العثور على جلسات نشطة.',
|
|
668
677
|
|
|
669
678
|
# Messages
|
|
@@ -743,6 +752,7 @@ MICROSYS_STRINGS = {
|
|
|
743
752
|
'msg_confirm_generate_backup': 'سيؤدي إنشاء رموز احتياطية جديدة إلى إلغاء صلاحية أي رموز سابقة. هل أنت متأكد من رغبتك في الاستمرار؟',
|
|
744
753
|
'msg_confirm_disable_2fa': 'هل أنت متأكد من رغبتك في تعطيل المصادقة الثنائية لهذه الطريقة؟',
|
|
745
754
|
'msg_confirm_sign_out_session': 'هل أنت متأكد من رغبتك في إنهاء جلسة هذا الجهاز؟',
|
|
755
|
+
'msg_confirm_trust_current_device': 'هل تريد اعتبار هذا الجهاز موثوقاً لمدة 30 يوماً؟',
|
|
746
756
|
'current_password_prompt': 'يرجى إدخال كلمة المرور الحالية للمتابعة.',
|
|
747
757
|
'current_password_required': 'يرجى إدخال كلمة المرور الحالية.',
|
|
748
758
|
'current_password_incorrect': 'كلمة المرور الحالية غير صحيحة.',
|
|
@@ -1172,6 +1182,8 @@ MICROSYS_STRINGS = {
|
|
|
1172
1182
|
'titlebar_surface_glass_desc': 'Blurred glass-style titlebar surface.',
|
|
1173
1183
|
'form_sys_email_2fa': 'Enable Email 2FA',
|
|
1174
1184
|
'help_sys_email_2fa': 'Allow users to enable two-factor authentication via email. Requires Microsys email delivery to be ready.',
|
|
1185
|
+
'form_sys_prevent_multiple_active_sessions': 'Prevent multiple active sessions',
|
|
1186
|
+
'help_sys_prevent_multiple_active_sessions': 'When enabled, a newly trusted session becomes this user’s only active session. Untrusted logins remain allowed but cannot force out trusted sessions.',
|
|
1175
1187
|
'form_sys_client_ip_mode': 'Client IP Source',
|
|
1176
1188
|
'help_sys_client_ip_mode': 'Choose which request header Microsys should trust when recording login, session, and security IP addresses.',
|
|
1177
1189
|
'client_ip_mode_x_forwarded_for': 'X-Forwarded-For',
|
|
@@ -1578,8 +1590,15 @@ MICROSYS_STRINGS = {
|
|
|
1578
1590
|
'current_session': 'Current Session',
|
|
1579
1591
|
'trusted_device_badge': 'Trusted Device',
|
|
1580
1592
|
'trusted_until': 'Trusted Until',
|
|
1593
|
+
'trust_this_device': 'Trust This Device',
|
|
1594
|
+
'trusted_device_added_success': 'This device is now trusted.',
|
|
1595
|
+
'session_revoke_trusted_denied': 'An untrusted session cannot sign out a trusted session.',
|
|
1596
|
+
'session_revoked_success': 'Session signed out.',
|
|
1597
|
+
'session_revoke_denied': 'That session does not belong to your account.',
|
|
1581
1598
|
'session_expires': 'Expires',
|
|
1582
1599
|
'current': 'Current',
|
|
1600
|
+
'protected': 'Protected',
|
|
1601
|
+
'sign_out': 'Sign Out',
|
|
1583
1602
|
'no_active_sessions': 'No active sessions were found.',
|
|
1584
1603
|
|
|
1585
1604
|
# Messages
|
|
@@ -1657,6 +1676,7 @@ MICROSYS_STRINGS = {
|
|
|
1657
1676
|
'msg_confirm_generate_backup': 'Generating new backup codes will invalidate any existing codes. Are you sure you want to proceed?',
|
|
1658
1677
|
'msg_confirm_disable_2fa': 'Are you sure you want to disable 2FA for this method?',
|
|
1659
1678
|
'msg_confirm_sign_out_session': 'Are you sure you want to sign out this device session?',
|
|
1679
|
+
'msg_confirm_trust_current_device': 'Trust this device for 30 days?',
|
|
1660
1680
|
'current_password_prompt': 'Please enter your current password to continue.',
|
|
1661
1681
|
'current_password_required': 'Please enter your current password.',
|
|
1662
1682
|
'current_password_incorrect': 'Current password is incorrect.',
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import secrets
|
|
3
|
+
from datetime import timedelta
|
|
4
|
+
|
|
5
|
+
from django.apps import apps
|
|
6
|
+
from django.contrib.sessions.models import Session
|
|
7
|
+
from django.core.signing import BadSignature
|
|
8
|
+
from django.db.models import Q
|
|
9
|
+
from django.utils import timezone
|
|
10
|
+
|
|
11
|
+
from .translations import get_strings
|
|
12
|
+
from .utils import get_client_ip, get_system_config
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
TRUSTED_DEVICE_COOKIE_NAME = 'microsys_trusted_device'
|
|
16
|
+
TRUSTED_DEVICE_COOKIE_SALT = 'microsys.trusted_device'
|
|
17
|
+
TRUSTED_DEVICE_MAX_AGE = 30 * 24 * 60 * 60
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def trusted_device_model():
|
|
21
|
+
return apps.get_model('microsys', 'TrustedDevice')
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def trusted_device_token_hash(raw_token):
|
|
25
|
+
return hashlib.sha256(str(raw_token or '').encode('utf-8')).hexdigest()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def device_label(user_agent):
|
|
29
|
+
s = get_strings()
|
|
30
|
+
user_agent = str(user_agent or '').strip()
|
|
31
|
+
lowered = user_agent.lower()
|
|
32
|
+
if not user_agent:
|
|
33
|
+
return s.get('device_unknown')
|
|
34
|
+
if 'edg/' in lowered or 'edge/' in lowered:
|
|
35
|
+
browser = 'Edge'
|
|
36
|
+
elif 'firefox/' in lowered:
|
|
37
|
+
browser = 'Firefox'
|
|
38
|
+
elif 'chrome/' in lowered or 'chromium/' in lowered:
|
|
39
|
+
browser = 'Chrome'
|
|
40
|
+
elif 'safari/' in lowered:
|
|
41
|
+
browser = 'Safari'
|
|
42
|
+
else:
|
|
43
|
+
browser = 'Browser'
|
|
44
|
+
if 'android' in lowered:
|
|
45
|
+
platform = 'Android'
|
|
46
|
+
elif 'iphone' in lowered or 'ipad' in lowered:
|
|
47
|
+
platform = 'iOS'
|
|
48
|
+
elif 'windows' in lowered:
|
|
49
|
+
platform = 'Windows'
|
|
50
|
+
elif 'mac os' in lowered or 'macintosh' in lowered:
|
|
51
|
+
platform = 'macOS'
|
|
52
|
+
elif 'linux' in lowered:
|
|
53
|
+
platform = 'Linux'
|
|
54
|
+
else:
|
|
55
|
+
platform = s.get('device_platform_generic')
|
|
56
|
+
return s.get('device_label_pattern').format(browser=browser, platform=platform)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def get_trusted_device_for_login(request, user):
|
|
60
|
+
if not request or not user:
|
|
61
|
+
return None
|
|
62
|
+
try:
|
|
63
|
+
raw_token = request.get_signed_cookie(
|
|
64
|
+
TRUSTED_DEVICE_COOKIE_NAME,
|
|
65
|
+
salt=TRUSTED_DEVICE_COOKIE_SALT,
|
|
66
|
+
)
|
|
67
|
+
except (KeyError, BadSignature):
|
|
68
|
+
return None
|
|
69
|
+
if not raw_token:
|
|
70
|
+
return None
|
|
71
|
+
return trusted_device_model().objects.filter(
|
|
72
|
+
user=user,
|
|
73
|
+
token_hash=trusted_device_token_hash(raw_token),
|
|
74
|
+
revoked_at__isnull=True,
|
|
75
|
+
trusted_until__gt=timezone.now(),
|
|
76
|
+
).first()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def sync_session_device_metadata(request, trusted_device=None):
|
|
80
|
+
session = getattr(request, 'session', None)
|
|
81
|
+
if session is None:
|
|
82
|
+
return {}
|
|
83
|
+
if not getattr(session, 'session_key', None):
|
|
84
|
+
session.save()
|
|
85
|
+
now = timezone.now().isoformat()
|
|
86
|
+
existing = session.get('microsys_device')
|
|
87
|
+
if not isinstance(existing, dict):
|
|
88
|
+
existing = {}
|
|
89
|
+
metadata = {
|
|
90
|
+
'user_agent': str(request.META.get('HTTP_USER_AGENT') or existing.get('user_agent') or '')[:500],
|
|
91
|
+
'ip_address': str(get_client_ip(request) or existing.get('ip_address') or '').strip(),
|
|
92
|
+
'first_seen': existing.get('first_seen') or now,
|
|
93
|
+
'last_seen': now,
|
|
94
|
+
}
|
|
95
|
+
if trusted_device is not None:
|
|
96
|
+
metadata['trusted_device_id'] = trusted_device.pk
|
|
97
|
+
metadata['trusted_until'] = trusted_device.trusted_until.isoformat()
|
|
98
|
+
elif existing.get('trusted_device_id'):
|
|
99
|
+
metadata['trusted_device_id'] = existing.get('trusted_device_id')
|
|
100
|
+
metadata['trusted_until'] = existing.get('trusted_until')
|
|
101
|
+
session['microsys_device'] = metadata
|
|
102
|
+
session.modified = True
|
|
103
|
+
if trusted_device is not None:
|
|
104
|
+
trusted_device.session_key = getattr(session, 'session_key', '') or ''
|
|
105
|
+
trusted_device.device_label = device_label(metadata.get('user_agent'))
|
|
106
|
+
trusted_device.ip_address = metadata.get('ip_address') or None
|
|
107
|
+
trusted_device.user_agent = metadata.get('user_agent') or ''
|
|
108
|
+
trusted_device.last_used_at = timezone.now()
|
|
109
|
+
trusted_device.save(update_fields=['session_key', 'device_label', 'ip_address', 'user_agent', 'last_used_at'])
|
|
110
|
+
return metadata
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def trusted_device_for_session(user, session_key, session_data=None):
|
|
114
|
+
if not user or not session_key:
|
|
115
|
+
return None
|
|
116
|
+
now = timezone.now()
|
|
117
|
+
metadata = {}
|
|
118
|
+
if isinstance(session_data, dict):
|
|
119
|
+
metadata = session_data.get('microsys_device') if isinstance(session_data.get('microsys_device'), dict) else {}
|
|
120
|
+
devices = trusted_device_model().objects.filter(
|
|
121
|
+
user=user,
|
|
122
|
+
revoked_at__isnull=True,
|
|
123
|
+
trusted_until__gt=now,
|
|
124
|
+
)
|
|
125
|
+
trusted_device_id = metadata.get('trusted_device_id')
|
|
126
|
+
if trusted_device_id is not None:
|
|
127
|
+
try:
|
|
128
|
+
by_id = devices.filter(pk=int(trusted_device_id)).first()
|
|
129
|
+
except (TypeError, ValueError):
|
|
130
|
+
by_id = None
|
|
131
|
+
if by_id is not None:
|
|
132
|
+
return by_id
|
|
133
|
+
return devices.filter(session_key=session_key).first()
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def current_session_trusted_device(request):
|
|
137
|
+
user = getattr(request, 'user', None)
|
|
138
|
+
if not user or not getattr(user, 'is_authenticated', False):
|
|
139
|
+
return None
|
|
140
|
+
session = getattr(request, 'session', None)
|
|
141
|
+
if session is None:
|
|
142
|
+
return None
|
|
143
|
+
return trusted_device_for_session(user, getattr(session, 'session_key', None), dict(session.items()))
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def revoke_linked_session_trust(user, session_keys, trusted_device_ids=None, exclude_trusted_device_id=None):
|
|
147
|
+
session_keys = [key for key in (session_keys or []) if key]
|
|
148
|
+
trusted_device_ids = [pk for pk in (trusted_device_ids or []) if pk]
|
|
149
|
+
devices = trusted_device_model().objects.filter(user=user, revoked_at__isnull=True)
|
|
150
|
+
if exclude_trusted_device_id:
|
|
151
|
+
devices = devices.exclude(pk=exclude_trusted_device_id)
|
|
152
|
+
criteria = Q()
|
|
153
|
+
if trusted_device_ids and session_keys:
|
|
154
|
+
criteria = Q(pk__in=trusted_device_ids) | Q(session_key__in=session_keys)
|
|
155
|
+
elif trusted_device_ids:
|
|
156
|
+
criteria = Q(pk__in=trusted_device_ids)
|
|
157
|
+
elif session_keys:
|
|
158
|
+
criteria = Q(session_key__in=session_keys)
|
|
159
|
+
else:
|
|
160
|
+
return 0
|
|
161
|
+
return devices.filter(criteria).update(revoked_at=timezone.now())
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def revoke_other_user_sessions(user, keep_session_key=None, keep_trusted_device_id=None):
|
|
165
|
+
now = timezone.now()
|
|
166
|
+
target_keys = []
|
|
167
|
+
trusted_ids = []
|
|
168
|
+
user_id = str(user.pk)
|
|
169
|
+
for session in Session.objects.filter(expire_date__gt=now):
|
|
170
|
+
if keep_session_key and session.session_key == keep_session_key:
|
|
171
|
+
continue
|
|
172
|
+
try:
|
|
173
|
+
data = session.get_decoded()
|
|
174
|
+
except Exception:
|
|
175
|
+
continue
|
|
176
|
+
if str(data.get('_auth_user_id') or '') != user_id:
|
|
177
|
+
continue
|
|
178
|
+
target_keys.append(session.session_key)
|
|
179
|
+
metadata = data.get('microsys_device') if isinstance(data.get('microsys_device'), dict) else {}
|
|
180
|
+
trusted_device_id = metadata.get('trusted_device_id')
|
|
181
|
+
if trusted_device_id is not None:
|
|
182
|
+
try:
|
|
183
|
+
trusted_ids.append(int(trusted_device_id))
|
|
184
|
+
except (TypeError, ValueError):
|
|
185
|
+
pass
|
|
186
|
+
if target_keys:
|
|
187
|
+
Session.objects.filter(session_key__in=target_keys).delete()
|
|
188
|
+
revoke_linked_session_trust(
|
|
189
|
+
user,
|
|
190
|
+
target_keys,
|
|
191
|
+
trusted_device_ids=trusted_ids,
|
|
192
|
+
exclude_trusted_device_id=keep_trusted_device_id,
|
|
193
|
+
)
|
|
194
|
+
return len(target_keys)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def enforce_single_active_trusted_session(request, user, trusted_device):
|
|
198
|
+
if not trusted_device:
|
|
199
|
+
return 0
|
|
200
|
+
if not bool(get_system_config().get('prevent_multiple_active_sessions', False)):
|
|
201
|
+
return 0
|
|
202
|
+
return revoke_other_user_sessions(
|
|
203
|
+
user,
|
|
204
|
+
keep_session_key=getattr(getattr(request, 'session', None), 'session_key', None),
|
|
205
|
+
keep_trusted_device_id=trusted_device.pk,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def issue_trusted_device(request, response, user):
|
|
210
|
+
raw_token = secrets.token_urlsafe(32)
|
|
211
|
+
trusted_device = trusted_device_model().objects.create(
|
|
212
|
+
user=user,
|
|
213
|
+
token_hash=trusted_device_token_hash(raw_token),
|
|
214
|
+
trusted_until=timezone.now() + timedelta(days=30),
|
|
215
|
+
)
|
|
216
|
+
sync_session_device_metadata(request, trusted_device=trusted_device)
|
|
217
|
+
response.set_signed_cookie(
|
|
218
|
+
TRUSTED_DEVICE_COOKIE_NAME,
|
|
219
|
+
raw_token,
|
|
220
|
+
salt=TRUSTED_DEVICE_COOKIE_SALT,
|
|
221
|
+
max_age=TRUSTED_DEVICE_MAX_AGE,
|
|
222
|
+
httponly=True,
|
|
223
|
+
secure=request.is_secure(),
|
|
224
|
+
samesite='Lax',
|
|
225
|
+
)
|
|
226
|
+
enforce_single_active_trusted_session(request, user, trusted_device)
|
|
227
|
+
return trusted_device
|
|
@@ -15,6 +15,7 @@ urlpatterns = [
|
|
|
15
15
|
path('accounts/register/verify/<str:token>/', views.register_verify_view, name='register_verify'),
|
|
16
16
|
path('accounts/profile/', views.user_profile, name='user_profile'),
|
|
17
17
|
path('accounts/profile/sessions/<str:session_key>/revoke/', views.revoke_profile_session, name='revoke_profile_session'),
|
|
18
|
+
path('accounts/profile/device/trust/', views.trust_current_device, name='trust_current_device'),
|
|
18
19
|
path('accounts/profile/edit/<str:pk>/modal/', views.DynamicModalManagerView.as_view(
|
|
19
20
|
model=views.User,
|
|
20
21
|
form_class=views.UserProfileEditForm,
|
|
@@ -1722,6 +1722,7 @@ SYSTEM_SETTINGS_EXPORT_FIELDS = (
|
|
|
1722
1722
|
'allow_user_language_override',
|
|
1723
1723
|
'default_table_density',
|
|
1724
1724
|
'email_2fa',
|
|
1725
|
+
'prevent_multiple_active_sessions',
|
|
1725
1726
|
'client_ip_config',
|
|
1726
1727
|
'public_root',
|
|
1727
1728
|
'public_root_split_enabled',
|
|
@@ -1862,6 +1863,7 @@ def normalize_system_settings_import_payload(payload):
|
|
|
1862
1863
|
'allow_user_font_override',
|
|
1863
1864
|
'allow_user_language_override',
|
|
1864
1865
|
'email_2fa',
|
|
1866
|
+
'prevent_multiple_active_sessions',
|
|
1865
1867
|
'public_root',
|
|
1866
1868
|
'public_root_split_enabled',
|
|
1867
1869
|
'public_registration_enabled',
|
|
@@ -1982,6 +1984,7 @@ def get_system_config():
|
|
|
1982
1984
|
'allow_user_language_override': True,
|
|
1983
1985
|
'default_table_density': DEFAULT_TABLE_DENSITY,
|
|
1984
1986
|
'email_2fa': False,
|
|
1987
|
+
'prevent_multiple_active_sessions': False,
|
|
1985
1988
|
'client_ip': default_client_ip_config(),
|
|
1986
1989
|
'public_root': False,
|
|
1987
1990
|
'public_root_split_enabled': False,
|
|
@@ -2105,6 +2108,14 @@ def get_system_config():
|
|
|
2105
2108
|
and _should_apply_db_override(bool(sys_settings.email_2fa), default_config['email_2fa'])
|
|
2106
2109
|
):
|
|
2107
2110
|
db_config['email_2fa'] = bool(sys_settings.email_2fa)
|
|
2111
|
+
if (
|
|
2112
|
+
hasattr(sys_settings, 'prevent_multiple_active_sessions')
|
|
2113
|
+
and _should_apply_db_override(
|
|
2114
|
+
bool(sys_settings.prevent_multiple_active_sessions),
|
|
2115
|
+
default_config['prevent_multiple_active_sessions'],
|
|
2116
|
+
)
|
|
2117
|
+
):
|
|
2118
|
+
db_config['prevent_multiple_active_sessions'] = bool(sys_settings.prevent_multiple_active_sessions)
|
|
2108
2119
|
client_ip_config = normalize_client_ip_config(getattr(sys_settings, 'client_ip_config', {}))
|
|
2109
2120
|
if _should_apply_db_override(client_ip_config, default_config['client_ip']):
|
|
2110
2121
|
db_config['client_ip'] = client_ip_config
|