Flask-Exts 0.2.3__tar.gz → 0.2.4__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.
- {flask_exts-0.2.3 → flask_exts-0.2.4}/CHANGES.rst +11 -1
- {flask_exts-0.2.3 → flask_exts-0.2.4}/PKG-INFO +4 -1
- {flask_exts-0.2.3 → flask_exts-0.2.4}/docs/configure.rst +1 -1
- {flask_exts-0.2.3 → flask_exts-0.2.4}/docs/index.rst +1 -0
- flask_exts-0.2.4/docs/user.rst +11 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/build_sample.py +4 -4
- flask_exts-0.2.4/examples/demo/templates/my/index.html +7 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/pyproject.toml +4 -1
- flask_exts-0.2.4/src/flask_exts/__about__.py +1 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/admin/base_admin.py +10 -1
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/commands.py +5 -5
- flask_exts-0.2.4/src/flask_exts/constants.py +6 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/datastore/sqla/models/user.py +10 -5
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/datastore/sqla/models/user_profile.py +3 -5
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/datastore/sqla/models/webauthn.py +4 -1
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/datastore/sqla/orm.py +2 -3
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/email/base.py +33 -10
- flask_exts-0.2.4/src/flask_exts/email/reset_password_sender.py +12 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/manager.py +7 -22
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/proxies.py +2 -2
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/security/core.py +6 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/security/email_verification.py +6 -5
- flask_exts-0.2.4/src/flask_exts/security/reset_password.py +78 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/security/serializer.py +2 -2
- flask_exts-0.2.4/src/flask_exts/security/two_factor_authentication.py +38 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/js/modal.js +1 -1
- flask_exts-0.2.4/src/flask_exts/static/js/qrcode.js +1945 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/base.py +8 -19
- flask_exts-0.2.4/src/flask_exts/template/dataset.py +9 -0
- flask_exts-0.2.4/src/flask_exts/template/forms/change_password.py +28 -0
- flask_exts-0.2.4/src/flask_exts/template/forms/forgot_password.py +19 -0
- {flask_exts-0.2.3/src/flask_exts → flask_exts-0.2.4/src/flask_exts/template}/forms/login.py +1 -1
- {flask_exts-0.2.3/src/flask_exts → flask_exts-0.2.4/src/flask_exts/template}/forms/profile.py +1 -1
- flask_exts-0.2.4/src/flask_exts/template/forms/recovery.py +10 -0
- {flask_exts-0.2.3/src/flask_exts → flask_exts-0.2.4/src/flask_exts/template}/forms/register.py +1 -1
- flask_exts-0.2.4/src/flask_exts/template/forms/reset_password.py +16 -0
- flask_exts-0.2.4/src/flask_exts/template/forms/two_factor.py +10 -0
- flask_exts-0.2.4/src/flask_exts/template/funcs.py +39 -0
- flask_exts-0.2.4/src/flask_exts/template/plugin.py +18 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/theme.py +5 -12
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/admin/base.html +3 -3
- flask_exts-0.2.4/src/flask_exts/templates/admin/master.html +1 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/admin/model/list.html +2 -2
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/base.html +4 -6
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/bootstrap4/form.html +4 -4
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/bootstrap5/form.html +6 -6
- flask_exts-0.2.4/src/flask_exts/templates/layout.html +12 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/macro/actions.html +2 -2
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/macro/form.html +1 -1
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/macro/icon.html +2 -2
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/macro/row_actions.html +2 -2
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/macro/table.html +8 -7
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/macro/vendor.html +0 -3
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/views/rediscli/response.html +1 -1
- flask_exts-0.2.4/src/flask_exts/templates/views/user/change_password.html +11 -0
- flask_exts-0.2.4/src/flask_exts/templates/views/user/forgot_password.html +11 -0
- flask_exts-0.2.4/src/flask_exts/templates/views/user/index.html +21 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/views/user/login.html +4 -3
- flask_exts-0.2.4/src/flask_exts/templates/views/user/recovery.html +10 -0
- flask_exts-0.2.4/src/flask_exts/templates/views/user/recovery_codes.html +32 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/views/user/register.html +1 -1
- flask_exts-0.2.4/src/flask_exts/templates/views/user/reset_password.html +11 -0
- flask_exts-0.2.4/src/flask_exts/templates/views/user/setup_tfa.html +44 -0
- flask_exts-0.2.4/src/flask_exts/templates/views/user/show_tfa.html +38 -0
- flask_exts-0.2.4/src/flask_exts/templates/views/user/verify_tfa.html +11 -0
- flask_exts-0.2.4/src/flask_exts/templates/views/user/verify_tfa_modal.html +17 -0
- flask_exts-0.2.4/src/flask_exts/usercenter/base_user.py +14 -0
- flask_exts-0.2.4/src/flask_exts/usercenter/base_user_store.py +29 -0
- flask_exts-0.2.4/src/flask_exts/usercenter/core.py +43 -0
- flask_exts-0.2.3/src/flask_exts/usercenter/sqla_usercenter.py → flask_exts-0.2.4/src/flask_exts/usercenter/sqla_user_store.py +10 -5
- flask_exts-0.2.4/src/flask_exts/utils/image.py +12 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/utils/request_user.py +4 -4
- flask_exts-0.2.4/src/flask_exts/views/user_view.py +300 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/security/authorizer/test_casbin_auth.py +14 -14
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/security/test_core.py +11 -3
- flask_exts-0.2.4/tests/security/test_tfa.py +18 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/test_base.py +1 -1
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/usercenter/test_sqla_usercenter.py +2 -2
- flask_exts-0.2.4/tests/utils/test_image.py +7 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/utils/test_request_user.py +2 -2
- flask_exts-0.2.4/tests/views/test_user_view.py +483 -0
- flask_exts-0.2.3/examples/demo/templates/my/index.html +0 -7
- flask_exts-0.2.3/src/flask_exts/__about__.py +0 -1
- flask_exts-0.2.3/src/flask_exts/template/utils.py +0 -35
- flask_exts-0.2.3/src/flask_exts/templates/admin/master.html +0 -1
- flask_exts-0.2.3/src/flask_exts/templates/layout.html +0 -21
- flask_exts-0.2.3/src/flask_exts/templates/views/user/index.html +0 -7
- flask_exts-0.2.3/src/flask_exts/usercenter/base_user.py +0 -7
- flask_exts-0.2.3/src/flask_exts/usercenter/base_usercenter.py +0 -40
- flask_exts-0.2.3/src/flask_exts/views/user_view.py +0 -132
- flask_exts-0.2.3/tests/views/test_user_view.py +0 -96
- {flask_exts-0.2.3 → flask_exts-0.2.4}/.github/workflows/release.yml +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/.gitignore +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/.readthedocs.yaml +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/LICENSE.rst +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/README.rst +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/babel/babel.cfg +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/babel/babel.sh +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/babel/messages.pot +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/docs/Makefile +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/docs/_static/logo.png +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/docs/changes.rst +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/docs/conf.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/docs/develop.rst +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/docs/examples.rst +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/docs/make.bat +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/docs/requirements.txt +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/README.rst +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap4/app.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap4/static/favicon.ico +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap4/templates/bootswatch.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap4/templates/flash.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap4/templates/form.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap4/templates/icon.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap4/templates/icons.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap4/templates/index.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap4/templates/mybase.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap4/templates/nav.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap4/templates/pagination.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap4/templates/table.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap5/app.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap5/static/favicon.ico +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap5/templates/bootswatch.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap5/templates/flash.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap5/templates/form.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap5/templates/icon.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap5/templates/icons.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap5/templates/index.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap5/templates/mybase.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap5/templates/nav.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap5/templates/pagination.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap5/templates/table.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/__init__.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/admin_views.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/file_op.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/make_app.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/models/__init__.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/models/author.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/models/keyword.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/models/location_image.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/models/post.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/models/post_tag.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/models/tag.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/models/tree.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/models/user.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/models/user_keyword.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/templates/field_list.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/templates/locations.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/templates/tree_list.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/views/author_view.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/views/keyword_view.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/views/location_image_view.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/views/my_view.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/views/post_view.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/views/tag_view.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/views/tree_view.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/views/user_view.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/fileadmin/__init__.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/fileadmin/file_view.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/fileadmin/make_app.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/fileadmin/tmp/bbb/a.txt +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/instance/config.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/rediscli.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/simple.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/requirements/build.in +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/requirements/test.in +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/__init__.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/admin/__init__.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/admin/actions.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/admin/admin.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/admin/menu.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/admin/model/__init__.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/admin/model/ajax.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/admin/model/filters.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/admin/model/form.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/admin/model/typefmt.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/admin/model/view.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/admin/row_action.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/admin/sqla/__init__.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/admin/sqla/ajax.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/admin/sqla/filters.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/admin/sqla/form.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/admin/sqla/typefmt.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/admin/sqla/view.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/admin/view.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/babel/__init__.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/babel/selector.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/datastore/__init__.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/datastore/sqla/__init__.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/datastore/sqla/models/__init__.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/datastore/sqla/models/role.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/email/__init__.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/email/sender.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/email/smtp.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/email/verify_email_sender.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/security/__init__.py +0 -0
- {flask_exts-0.2.3/src/flask_exts/forms → flask_exts-0.2.4/src/flask_exts/security/authorizer}/__init__.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/security/authorizer/base.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/security/authorizer/casbin_authorizer.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/security/authorizer/sqlalchemy_adapter.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/security/hasher.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/security/principal/identity.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/security/principal/identity_context.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/security/principal/permission.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/security/principal/principal.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/signals.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/css/admin.css +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/css/rediscli.css +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/js/actions.js +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/js/details_filter.js +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/js/filters.js +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/js/form.js +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/js/helper.js +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/js/rediscli.js +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/js/security/base64.js +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/js/security/webauthn.js +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/logo.png +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/bootstrap-icons/bootstrap-icons.svg +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/bootstrap-icons/font/bootstrap-icons.css +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/bootstrap-icons/font/bootstrap-icons.json +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/bootstrap-icons/font/bootstrap-icons.min.css +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/bootstrap-icons/font/bootstrap-icons.scss +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/bootstrap-icons/font/fonts/bootstrap-icons.woff +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/bootstrap-icons/font/fonts/bootstrap-icons.woff2 +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/bootstrap4/bootstrap.bundle.min.js +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/bootstrap4/bootstrap.min.css +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/bootstrap5/bootstrap.bundle.min.js +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/bootstrap5/bootstrap.min.css +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/daterangepicker/daterangepicker.css +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/daterangepicker/daterangepicker.js +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/jquery/jquery.min.js +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/moment.min.js +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/select2/select2.min.css +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/select2/select2.min.js +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/vanilla-editor/css/bootstrap-editable.css +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/vanilla-editor/img/clear.png +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/vanilla-editor/img/loading.gif +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/vanilla-editor/js/bootstrap-editable.min.js +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/__init__.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/fields/__init__.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/fields/ajax_select.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/fields/datetime.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/fields/file.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/fields/inline.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/fields/json.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/fields/recaptcha.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/fields/select.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/fields/sqla.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/fields/switch.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/fields/taglist.py +0 -0
- {flask_exts-0.2.3/src/flask_exts/security/authorizer → flask_exts-0.2.4/src/flask_exts/template/form}/__init__.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/form/base_form.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/form/csrf.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/form/flask_form.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/form/form_opts.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/form/meta.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/form/session_csrf.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/form/utils.py +0 -0
- {flask_exts-0.2.3/src/flask_exts/template/form → flask_exts-0.2.4/src/flask_exts/template/forms}/__init__.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/rules.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/validators/__init__.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/validators/field_list.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/validators/file.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/validators/recaptcha.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/validators/sqla.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/widgets/__init__.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/widgets/checkbox.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/widgets/datetime.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/widgets/file.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/widgets/inline.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/widgets/recaptcha.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/widgets/render_template.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/widgets/select.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/widgets/xeditable.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/admin/index.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/admin/model/create.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/admin/model/details.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/admin/model/edit.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/admin/model/modals/create.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/admin/model/modals/details.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/admin/model/modals/edit.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/bootstrap4/message.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/bootstrap4/pagination.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/bootstrap5/message.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/index.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/macro/inline.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/macro/layout.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/macro/menu.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/macro/message.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/macro/nav.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/macro/pagination.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/views/file/form.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/views/file/list.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/views/file/modals/form.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/views/rediscli/console.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/views/user/list.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/views/user/verify_email.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/widgets/inline_field_list.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/widgets/inline_form.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/translations/__init__.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/translations/en/LC_MESSAGES/messages.mo +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/translations/en/LC_MESSAGES/messages.po +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/translations/zh_CN/LC_MESSAGES/messages.mo +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/translations/zh_CN/LC_MESSAGES/messages.po +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/types.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/usercenter/__init__.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/utils/__init__.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/utils/authorize.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/utils/decorators.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/utils/flask.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/utils/jwt.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/utils/sqla.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/utils/tools.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/utils/url.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/views/file_view/__init__.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/views/file_view/base_file_view.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/views/file_view/local_file_storage.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/views/file_view/local_file_view.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/views/index_view.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/views/rediscli_view.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/__init__.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/admin/__init__.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/admin/customer/conftest.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/admin/customer/templates/mock.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/admin/customer/test_admin.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/admin/sqla/__init__.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/admin/sqla/templates/another_macro.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/admin/sqla/templates/macro.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/admin/sqla/test_basic.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/admin/sqla/test_form_rules.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/admin/sqla/test_inlineform.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/admin/sqla/test_multi_pk.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/admin/sqla/test_translation.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/admin/test_base.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/admin/test_model.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/conftest.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/data/copyleft.gif +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/data/copyleft.jpeg +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/data/copyleft.jpg +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/data/copyleft.png +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/data/copyleft.tiff +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/email/test_base.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/email/test_smtp.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/helper.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/models/__init__.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/models/message.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/models/model1.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/models/multpk.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/models/polymorphic.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/models/tree.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/models/user.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/security/authorizer/__init__.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/security/authorizer/casbin_adapter/rbac_model.conf +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/security/authorizer/casbin_adapter/rbac_policy.csv +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/security/authorizer/casbin_adapter/test_sqlalchemy_adapter.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/security/authorizer/casbin_files/authz_policy.csv +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/security/authorizer/casbin_files/rbac_model.conf +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/security/authorizer/casbin_files/rbac_policy.csv +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/security/test_principal.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/__init__.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/bootstrap4/__init__.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/bootstrap4/test_render_field.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/bootstrap4/test_render_form.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/bootstrap4/test_render_form_row.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/bootstrap4/test_render_hidden_errors.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/bootstrap4/test_render_icon_sprite.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/bootstrap4/test_render_messages.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/bootstrap4/test_render_pager.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/bootstrap4/test_render_pagination.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/bootstrap4/test_render_table.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/bootstrap5/__init__.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/bootstrap5/conftest.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/bootstrap5/test_base.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/bootstrap5/test_render_form.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/bootstrap5/test_render_form_row.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/bootstrap5/test_render_messages.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/bootstrap5/test_render_pagination.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/conftest.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/forms/test_csrf.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/forms/test_fields.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/forms/test_filefield.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/forms/test_uploadfile.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/macros/test_render_breadcrumb_item.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/macros/test_render_nav_item.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/test_base.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/templates/method.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/templates/mock.html +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/test_babel.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/test_flask.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/translations/en/LC_MESSAGES/messages.mo +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/translations/en/LC_MESSAGES/messages.po +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/translations/zh_CN/LC_MESSAGES/messages.po +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/utils/test_url.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/views/files/dummy.txt +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/views/test_file_view.py +0 -0
- {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/views/test_rediscli_view.py +0 -0
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
v0.2.4
|
|
2
|
+
------
|
|
3
|
+
|
|
4
|
+
Unreleased
|
|
5
|
+
|
|
6
|
+
- user change password
|
|
7
|
+
- user forgot password
|
|
8
|
+
- user 2FA with TOTP
|
|
9
|
+
- user recovery codes
|
|
10
|
+
- user recovery
|
|
11
|
+
|
|
1
12
|
v0.2.3
|
|
2
13
|
------
|
|
3
14
|
|
|
@@ -7,7 +18,6 @@ Released 2025-08-28
|
|
|
7
18
|
- add folder email
|
|
8
19
|
- add security.email_verification
|
|
9
20
|
|
|
10
|
-
|
|
11
21
|
v0.2.2
|
|
12
22
|
------
|
|
13
23
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Flask-Exts
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: Flask extensions with admin, SQLAlchemy, babel, forms, fields, widgets, and so on.
|
|
5
5
|
Project-URL: Documentation, https://flask-exts.readthedocs.io
|
|
6
6
|
Project-URL: Source, https://github.com/ojso/flask-exts
|
|
@@ -46,7 +46,10 @@ Requires-Dist: flask-babel>=4.0
|
|
|
46
46
|
Requires-Dist: flask-login>=0.6
|
|
47
47
|
Requires-Dist: flask-sqlalchemy>=3.0
|
|
48
48
|
Requires-Dist: flask>=3.0
|
|
49
|
+
Requires-Dist: pillow>=11.0
|
|
49
50
|
Requires-Dist: pyjwt>=2.0
|
|
51
|
+
Requires-Dist: pyotp>=2.0
|
|
52
|
+
Requires-Dist: qrcode>=8.0
|
|
50
53
|
Requires-Dist: tablib>=3.0
|
|
51
54
|
Requires-Dist: webauthn>=2.0
|
|
52
55
|
Requires-Dist: wtforms>=3.0
|
|
@@ -18,7 +18,7 @@ Configuration
|
|
|
18
18
|
Default is ``csrf_token``.
|
|
19
19
|
``CSRF_TIME_LIMIT`` Max age in seconds for CSRF tokens.
|
|
20
20
|
Default is ``1800``.
|
|
21
|
-
``
|
|
21
|
+
``NOREPLY_EMAIL_SENDER`` Email address used to send emails.
|
|
22
22
|
It is a dict for smtp with keys: host,port,user,password.
|
|
23
23
|
Default is None.
|
|
24
24
|
========================== =====================================================================
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import random
|
|
2
2
|
import datetime
|
|
3
|
-
from flask_exts.proxies import
|
|
3
|
+
from flask_exts.proxies import _userstore
|
|
4
4
|
from .models import db
|
|
5
5
|
from .models.author import Author
|
|
6
6
|
from .models.author import AVAILABLE_USER_TYPES
|
|
@@ -13,11 +13,11 @@ from .models.location_image import ImageType
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def build_user_admin():
|
|
16
|
-
u, _ =
|
|
16
|
+
u, _ = _userstore.create_user(
|
|
17
17
|
username="admin", password="admin", email="admin@example.com"
|
|
18
18
|
)
|
|
19
|
-
r, _ =
|
|
20
|
-
|
|
19
|
+
r, _ = _userstore.create_role(name="admin")
|
|
20
|
+
_userstore.user_add_role(u, r)
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
def build_sample_image_type():
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.4"
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from flask import render_template
|
|
2
2
|
from flask import url_for
|
|
3
|
+
from flask import make_response
|
|
3
4
|
from .menu import Menu
|
|
4
5
|
|
|
5
6
|
|
|
@@ -122,4 +123,12 @@ class BaseAdmin:
|
|
|
122
123
|
# Add URL generation method to kwargs
|
|
123
124
|
kwargs["get_url"] = self.get_url
|
|
124
125
|
|
|
125
|
-
|
|
126
|
+
# If _headers is provided in kwargs, use make_response to set headers
|
|
127
|
+
if "_headers" in kwargs:
|
|
128
|
+
headers = kwargs.pop("_headers")
|
|
129
|
+
response = make_response(render_template(template, **kwargs))
|
|
130
|
+
for key, value in headers.items():
|
|
131
|
+
response.headers[key] = value
|
|
132
|
+
return response
|
|
133
|
+
else:
|
|
134
|
+
return render_template(template, **kwargs)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import click
|
|
2
2
|
from flask.cli import AppGroup
|
|
3
|
-
from .proxies import
|
|
3
|
+
from .proxies import _userstore
|
|
4
4
|
from .datastore.sqla import db
|
|
5
5
|
|
|
6
6
|
|
|
@@ -20,15 +20,15 @@ security_cli = AppGroup("security", short_help="security for the app.")
|
|
|
20
20
|
@click.argument("name")
|
|
21
21
|
def create_user(name):
|
|
22
22
|
print(f"security create_user {name}")
|
|
23
|
-
result =
|
|
23
|
+
result = _userstore.create_user(username=name)
|
|
24
24
|
click.echo(result)
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
@security_cli.command("create_admin", help="create user:admin with admin:role")
|
|
28
28
|
@click.argument("password", default="admin")
|
|
29
29
|
def create_admin(password):
|
|
30
|
-
u, _ =
|
|
31
|
-
r, _ =
|
|
32
|
-
|
|
30
|
+
u, _ = _userstore.create_user(username="admin", password=password)
|
|
31
|
+
r, _ = _userstore.create_role(name="admin")
|
|
32
|
+
_userstore.user_add_role(u, r)
|
|
33
33
|
|
|
34
34
|
print(f"security create admin {u.username} with role {r.name} ")
|
|
@@ -10,6 +10,8 @@ from ..orm import relationship
|
|
|
10
10
|
from ..orm import ForeignKey
|
|
11
11
|
from ..orm import Table
|
|
12
12
|
from ..orm import Column
|
|
13
|
+
from ..orm import MutableList
|
|
14
|
+
from ..orm import JSON
|
|
13
15
|
from .role import Role
|
|
14
16
|
from .user_profile import UserProfile
|
|
15
17
|
|
|
@@ -28,31 +30,34 @@ class User(db.Model, BaseUser):
|
|
|
28
30
|
uuid: Mapped[str] = mapped_column(unique=True, default=lambda: str(uuid.uuid4()))
|
|
29
31
|
username: Mapped[Optional[str]] = mapped_column(unique=True)
|
|
30
32
|
password: Mapped[Optional[str]]
|
|
31
|
-
|
|
32
|
-
avatar: Mapped[Optional[str]]
|
|
33
|
+
actived: Mapped[bool] = mapped_column(default=False)
|
|
33
34
|
status: Mapped[int] = mapped_column(default=0)
|
|
34
35
|
expired_at: Mapped[Optional[datetime]]
|
|
35
|
-
actived: Mapped[bool] = mapped_column(default=False)
|
|
36
36
|
email: Mapped[Optional[str]] = mapped_column(unique=True)
|
|
37
37
|
email_verified: Mapped[bool] = mapped_column(default=False)
|
|
38
38
|
email_verified_at: Mapped[Optional[datetime]]
|
|
39
39
|
phone_number: Mapped[Optional[str]] = mapped_column(unique=True)
|
|
40
40
|
phone_verified: Mapped[bool] = mapped_column(default=False)
|
|
41
41
|
phone_verified_at: Mapped[Optional[datetime]]
|
|
42
|
+
tfa_enabled: Mapped[bool] = mapped_column(default=False)
|
|
43
|
+
tfa_method: Mapped[Optional[str]]
|
|
44
|
+
totp_secret: Mapped[Optional[str]]
|
|
45
|
+
recovery_codes: Mapped[Optional[list[str]]] = mapped_column(
|
|
46
|
+
type_=MutableList.as_mutable(JSON)
|
|
47
|
+
)
|
|
42
48
|
created_at: Mapped[datetime] = mapped_column(default=datetime.now)
|
|
43
49
|
updated_at: Mapped[datetime] = mapped_column(
|
|
44
50
|
default=datetime.now, onupdate=datetime.now
|
|
45
51
|
)
|
|
46
52
|
|
|
47
53
|
roles: Mapped[List["Role"]] = relationship(secondary=user_role_table)
|
|
48
|
-
|
|
49
54
|
profile: Mapped["UserProfile"] = relationship(
|
|
50
55
|
"UserProfile", back_populates="user", uselist=False
|
|
51
56
|
)
|
|
52
57
|
|
|
53
58
|
def get_roles(self):
|
|
54
59
|
return [r.name for r in self.roles]
|
|
55
|
-
|
|
60
|
+
|
|
56
61
|
@property
|
|
57
62
|
def is_active(self):
|
|
58
63
|
return self.actived
|
|
@@ -14,12 +14,10 @@ class UserProfile(db.Model):
|
|
|
14
14
|
user_id: Mapped[int] = mapped_column(ForeignKey("user.id"), unique=True)
|
|
15
15
|
name: Mapped[Optional[str]]
|
|
16
16
|
identity: Mapped[Optional[str]] = mapped_column(unique=True)
|
|
17
|
+
display_name: Mapped[Optional[str]]
|
|
18
|
+
avatar: Mapped[Optional[str]]
|
|
17
19
|
locale: Mapped[Optional[str]]
|
|
18
|
-
timezone: Mapped[Optional[str]]
|
|
19
|
-
# 2FA
|
|
20
|
-
tf_method: Mapped[Optional[str]]
|
|
21
|
-
tf_totp_secret: Mapped[Optional[str]]
|
|
22
|
-
recovery_codes: Mapped[Optional[str]]
|
|
20
|
+
timezone: Mapped[Optional[str]]
|
|
23
21
|
# timestamps
|
|
24
22
|
created_at: Mapped[datetime] = mapped_column(default=datetime.now)
|
|
25
23
|
updated_at: Mapped[datetime] = mapped_column(
|
|
@@ -8,6 +8,7 @@ from ..orm import LargeBinary
|
|
|
8
8
|
from ..orm import ForeignKey
|
|
9
9
|
from ..orm import relationship
|
|
10
10
|
from ..orm import JSON
|
|
11
|
+
from ..orm import MutableList
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
class WebAuthnMixin(db.Model):
|
|
@@ -19,7 +20,9 @@ class WebAuthnMixin(db.Model):
|
|
|
19
20
|
)
|
|
20
21
|
public_key: Mapped[bytes] = mapped_column(LargeBinary)
|
|
21
22
|
sign_count: Mapped[Optional[int]] = mapped_column(default=0)
|
|
22
|
-
transports: Mapped[Optional[list[str]]] = mapped_column(
|
|
23
|
+
transports: Mapped[Optional[list[str]]] = mapped_column(
|
|
24
|
+
type_=MutableList.as_mutable(JSON)
|
|
25
|
+
)
|
|
23
26
|
backup_state: Mapped[bool] = mapped_column()
|
|
24
27
|
device_type: Mapped[str] = mapped_column()
|
|
25
28
|
extensions: Mapped[Optional[str]] = mapped_column()
|
|
@@ -7,6 +7,5 @@ from sqlalchemy.orm import mapped_column
|
|
|
7
7
|
from sqlalchemy.orm import declared_attr
|
|
8
8
|
from sqlalchemy.types import LargeBinary
|
|
9
9
|
from sqlalchemy.types import JSON
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
from sqlalchemy.ext.mutable import MutableDict
|
|
11
|
+
from sqlalchemy.ext.mutable import MutableList
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from ..signals import to_send_email
|
|
2
2
|
from .verify_email_sender import VerifyEmailSender
|
|
3
|
+
from .reset_password_sender import ResetPasswordSender
|
|
4
|
+
|
|
3
5
|
|
|
4
6
|
class Email:
|
|
5
7
|
def __init__(self, app=None):
|
|
@@ -11,6 +13,15 @@ class Email:
|
|
|
11
13
|
def init_app(self, app):
|
|
12
14
|
self.app = app
|
|
13
15
|
self.subscribe_signal(app)
|
|
16
|
+
self.register_senders()
|
|
17
|
+
if "verify_email" not in self.senders:
|
|
18
|
+
app.logger.warning(
|
|
19
|
+
"Please configure the NOREPLY_EMAIL_SENDER in your app config."
|
|
20
|
+
)
|
|
21
|
+
if "reset_password" not in self.senders:
|
|
22
|
+
app.logger.warning(
|
|
23
|
+
"Please configure the NOREPLY_EMAIL_SENDER in your app config."
|
|
24
|
+
)
|
|
14
25
|
|
|
15
26
|
def subscribe_signal(self, app):
|
|
16
27
|
to_send_email.connect(self.send, app)
|
|
@@ -21,23 +32,35 @@ class Email:
|
|
|
21
32
|
return
|
|
22
33
|
self.send_email(data["type"], data)
|
|
23
34
|
|
|
24
|
-
def register_sender(self, name, sender):
|
|
25
|
-
"""Register a sender with a name."""
|
|
26
|
-
if name in self.senders:
|
|
27
|
-
raise ValueError(f"Sender with name {name} already exists.")
|
|
28
|
-
self.senders[name] = sender
|
|
29
|
-
|
|
30
35
|
def send_email(self, name, data):
|
|
31
36
|
"""Send an email using a registered sender."""
|
|
32
37
|
if name not in self.senders:
|
|
33
|
-
self.app.logger.warning(
|
|
38
|
+
self.app.logger.warning(
|
|
39
|
+
f"Email can not send data: {data}, because it can not find the sender: {name}"
|
|
40
|
+
)
|
|
34
41
|
# raise ValueError(f"Email can not find the sender: {name}")
|
|
35
42
|
return
|
|
36
43
|
sender = self.senders.get(name)
|
|
37
44
|
return sender.send(data)
|
|
38
|
-
|
|
45
|
+
|
|
39
46
|
def register_senders(self):
|
|
40
47
|
"""Register multiple senders."""
|
|
41
|
-
|
|
42
|
-
|
|
48
|
+
# Register VerifyEmailSender
|
|
49
|
+
if "NOREPLY_EMAIL_SENDER" in self.app.config:
|
|
50
|
+
verify_email_sender = VerifyEmailSender(
|
|
51
|
+
**self.app.config["NOREPLY_EMAIL_SENDER"]
|
|
52
|
+
)
|
|
43
53
|
self.register_sender("verify_email", verify_email_sender)
|
|
54
|
+
|
|
55
|
+
# Register ResetPasswordSender
|
|
56
|
+
if "NOREPLY_EMAIL_SENDER" in self.app.config:
|
|
57
|
+
reset_password_sender = ResetPasswordSender(
|
|
58
|
+
**self.app.config["NOREPLY_EMAIL_SENDER"]
|
|
59
|
+
)
|
|
60
|
+
self.register_sender("reset_password", reset_password_sender)
|
|
61
|
+
|
|
62
|
+
def register_sender(self, name, sender):
|
|
63
|
+
"""Register a sender with a name."""
|
|
64
|
+
if name in self.senders:
|
|
65
|
+
raise ValueError(f"Sender with name {name} already exists.")
|
|
66
|
+
self.senders[name] = sender
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .smtp import SmtpSSL
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ResetPasswordSender(SmtpSSL):
|
|
5
|
+
def create_emsg(self, data):
|
|
6
|
+
if "subject" not in data:
|
|
7
|
+
data["subject"] = "Please reset your password"
|
|
8
|
+
if "content" not in data:
|
|
9
|
+
data["content"] = data.get("reset_password_link", "")
|
|
10
|
+
if "to" not in data:
|
|
11
|
+
data["to"] = data.get("email")
|
|
12
|
+
return super().create_emsg(data)
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
from flask_login import LoginManager
|
|
2
1
|
from .datastore.sqla import db
|
|
3
2
|
from .babel import babel_init_app
|
|
4
3
|
from .template.base import Template
|
|
5
4
|
from .email.base import Email
|
|
6
|
-
from .usercenter.
|
|
7
|
-
from .utils.request_user import load_user_from_request
|
|
5
|
+
from .usercenter.core import UserCenter
|
|
8
6
|
from .security.core import Security
|
|
9
7
|
|
|
10
8
|
|
|
@@ -22,8 +20,10 @@ class Manager:
|
|
|
22
20
|
def get_email(self):
|
|
23
21
|
return Email()
|
|
24
22
|
|
|
25
|
-
def
|
|
26
|
-
|
|
23
|
+
def get_admin_class(self):
|
|
24
|
+
from .admin.base_admin import BaseAdmin
|
|
25
|
+
|
|
26
|
+
return BaseAdmin
|
|
27
27
|
|
|
28
28
|
def init_app(self, app):
|
|
29
29
|
self.app = app
|
|
@@ -54,21 +54,11 @@ class Manager:
|
|
|
54
54
|
# init email
|
|
55
55
|
self.email = self.get_email()
|
|
56
56
|
self.email.init_app(app)
|
|
57
|
-
self.email.register_senders()
|
|
58
57
|
|
|
59
|
-
# init usercenter
|
|
60
|
-
self.usercenter =
|
|
58
|
+
# init usercenter and initial login manager in usercenter
|
|
59
|
+
self.usercenter = UserCenter()
|
|
61
60
|
self.usercenter.init_app(app)
|
|
62
61
|
|
|
63
|
-
# login
|
|
64
|
-
if not hasattr(app, "login_manager"):
|
|
65
|
-
login_manager = LoginManager()
|
|
66
|
-
login_manager.init_app(app)
|
|
67
|
-
login_manager.login_view = "user.login"
|
|
68
|
-
# login_manager.login_message = "Please login in"
|
|
69
|
-
login_manager.user_loader(self.usercenter.user_loader)
|
|
70
|
-
login_manager.request_loader(load_user_from_request)
|
|
71
|
-
|
|
72
62
|
# init security
|
|
73
63
|
self.security = Security()
|
|
74
64
|
self.security.init_app(app)
|
|
@@ -76,8 +66,3 @@ class Manager:
|
|
|
76
66
|
# init admin
|
|
77
67
|
self.admin = self.get_admin_class()()
|
|
78
68
|
self.admin.init_app(app)
|
|
79
|
-
|
|
80
|
-
def get_admin_class(self):
|
|
81
|
-
from .admin.base_admin import BaseAdmin
|
|
82
|
-
|
|
83
|
-
return BaseAdmin
|
|
@@ -6,7 +6,7 @@ from werkzeug.local import LocalProxy
|
|
|
6
6
|
if t.TYPE_CHECKING:
|
|
7
7
|
from .manager import Manager
|
|
8
8
|
from .template.base import Template
|
|
9
|
-
from .usercenter.
|
|
9
|
+
from .usercenter.base_user_store import BaseUserStore
|
|
10
10
|
from .security.core import Security
|
|
11
11
|
from .security.authorizer.base import Authorizer
|
|
12
12
|
|
|
@@ -15,7 +15,7 @@ _manager: "Manager" = LocalProxy(lambda: current_app.extensions["manager"])
|
|
|
15
15
|
|
|
16
16
|
_template: "Template" = LocalProxy(lambda: _manager.template)
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
_userstore: "BaseUserStore" = LocalProxy(lambda: _manager.usercenter.userstore)
|
|
19
19
|
|
|
20
20
|
_security: "Security" = LocalProxy(lambda: _manager.security)
|
|
21
21
|
|
|
@@ -2,6 +2,8 @@ from .hasher import Blake2bHasher
|
|
|
2
2
|
from .serializer import TimedUrlSerializer
|
|
3
3
|
from .authorizer.casbin_authorizer import CasbinAuthorizer
|
|
4
4
|
from .email_verification import EmailVerification
|
|
5
|
+
from .reset_password import ResetPassword
|
|
6
|
+
from .two_factor_authentication import TwoFactorAuthentication
|
|
5
7
|
|
|
6
8
|
|
|
7
9
|
class Security:
|
|
@@ -19,8 +21,12 @@ class Security:
|
|
|
19
21
|
self.serializer = TimedUrlSerializer(secret_key)
|
|
20
22
|
# email verification
|
|
21
23
|
self.email_verification = EmailVerification(app)
|
|
24
|
+
# reset password
|
|
25
|
+
self.reset_password = ResetPassword(app)
|
|
22
26
|
# authorizer
|
|
23
27
|
self.authorizer = CasbinAuthorizer(app)
|
|
28
|
+
# 2FA
|
|
29
|
+
self.tfa = TwoFactorAuthentication(app)
|
|
24
30
|
|
|
25
31
|
def get_within(self, serializer_name):
|
|
26
32
|
"""Get the max age for a serializer."""
|
|
@@ -2,7 +2,7 @@ from datetime import datetime
|
|
|
2
2
|
from flask import url_for
|
|
3
3
|
from flask import current_app
|
|
4
4
|
from ..proxies import _security
|
|
5
|
-
from ..proxies import
|
|
5
|
+
from ..proxies import _userstore
|
|
6
6
|
from ..signals import to_send_email
|
|
7
7
|
|
|
8
8
|
|
|
@@ -30,7 +30,8 @@ class EmailVerification:
|
|
|
30
30
|
|
|
31
31
|
:param user: The user to send the instructions to
|
|
32
32
|
"""
|
|
33
|
-
|
|
33
|
+
if user is None or user.email is None:
|
|
34
|
+
return
|
|
34
35
|
token = self.generate_verify_email_token(user)
|
|
35
36
|
link = url_for("user.verify_email", token=token, _external=True)
|
|
36
37
|
|
|
@@ -44,7 +45,7 @@ class EmailVerification:
|
|
|
44
45
|
|
|
45
46
|
to_send_email.send(current_app._get_current_object(), data=data)
|
|
46
47
|
|
|
47
|
-
def
|
|
48
|
+
def verify_email_with_token(self, token, within=None):
|
|
48
49
|
"""
|
|
49
50
|
View function which handles an email verification request.
|
|
50
51
|
This is always a GET from an email - so for 'spa' must always redirect.
|
|
@@ -63,7 +64,7 @@ class EmailVerification:
|
|
|
63
64
|
return ("invalid_token", None)
|
|
64
65
|
|
|
65
66
|
token_user_identity, token_email_hash = token_data
|
|
66
|
-
user =
|
|
67
|
+
user = _userstore.get_user_by_identity(token_user_identity)
|
|
67
68
|
|
|
68
69
|
if not user:
|
|
69
70
|
return ("no_user", None)
|
|
@@ -78,6 +79,6 @@ class EmailVerification:
|
|
|
78
79
|
user.email_verified_at = datetime.now()
|
|
79
80
|
user.actived = True
|
|
80
81
|
|
|
81
|
-
|
|
82
|
+
_userstore.save_user(user)
|
|
82
83
|
|
|
83
84
|
return ("verified", user)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from flask import url_for
|
|
3
|
+
from flask import current_app
|
|
4
|
+
from ..proxies import _security
|
|
5
|
+
from ..proxies import _userstore
|
|
6
|
+
from ..signals import to_send_email
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ResetPassword:
|
|
10
|
+
"""
|
|
11
|
+
Class to handle reset password functionality.
|
|
12
|
+
This class provides methods to generate reset token and reset password with the token.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, app=None):
|
|
16
|
+
self.app = app
|
|
17
|
+
self.serializer_name = "reset_password"
|
|
18
|
+
|
|
19
|
+
def generate_reset_password_token(self, user):
|
|
20
|
+
"""Generates a reset password token for the specified user.
|
|
21
|
+
:param user: The user to work with
|
|
22
|
+
"""
|
|
23
|
+
data = (str(user.id), _security.hasher.hash(user.email))
|
|
24
|
+
token = _security.serializer.dumps(self.serializer_name, data)
|
|
25
|
+
return token
|
|
26
|
+
|
|
27
|
+
def send_reset_password_token(self, user):
|
|
28
|
+
"""Sends the reset password token.
|
|
29
|
+
:param user: The user to send the reset password token to.
|
|
30
|
+
"""
|
|
31
|
+
if user is None or user.email is None:
|
|
32
|
+
return
|
|
33
|
+
token = self.generate_reset_password_token(user)
|
|
34
|
+
link = url_for("user.reset_password", token=token, _external=True)
|
|
35
|
+
|
|
36
|
+
data = {
|
|
37
|
+
"type": "reset_password",
|
|
38
|
+
"email": user.email,
|
|
39
|
+
"reset_password_link": link,
|
|
40
|
+
"reset_password_token": token,
|
|
41
|
+
"user": user,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
to_send_email.send(current_app._get_current_object(), data=data)
|
|
45
|
+
|
|
46
|
+
def reset_password_with_token(self, token, password, within=None):
|
|
47
|
+
"""
|
|
48
|
+
Resets the password for a user with the given token.
|
|
49
|
+
"""
|
|
50
|
+
if within is None:
|
|
51
|
+
within = _security.get_within(self.serializer_name)
|
|
52
|
+
|
|
53
|
+
expired, invalid, token_data = _security.serializer.loads(
|
|
54
|
+
self.serializer_name, token, within
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
if expired:
|
|
58
|
+
return ("expired", None)
|
|
59
|
+
|
|
60
|
+
if invalid or not token_data:
|
|
61
|
+
return ("invalid_token", None)
|
|
62
|
+
|
|
63
|
+
token_user_identity, token_email_hash = token_data
|
|
64
|
+
user = _userstore.get_user_by_identity(token_user_identity)
|
|
65
|
+
|
|
66
|
+
if not user:
|
|
67
|
+
return ("no_user", None)
|
|
68
|
+
|
|
69
|
+
if not _security.hasher.verify(user.email, token_email_hash):
|
|
70
|
+
return ("invalid_token", None)
|
|
71
|
+
|
|
72
|
+
if not user.email_verified:
|
|
73
|
+
return ("email_not_verified", None)
|
|
74
|
+
|
|
75
|
+
_userstore.user_set(user, password=user.hash_password(password))
|
|
76
|
+
_userstore.save_user(user)
|
|
77
|
+
|
|
78
|
+
return ("ok", None)
|
|
@@ -22,7 +22,7 @@ class TimedUrlSerializer:
|
|
|
22
22
|
return token
|
|
23
23
|
|
|
24
24
|
def loads(
|
|
25
|
-
self, serializer_name: str, token: str, max_age: int | None =
|
|
25
|
+
self, serializer_name: str, token: str, max_age: int | None = 259200 # 3 days
|
|
26
26
|
) -> tuple[bool, bool, t.Any]:
|
|
27
27
|
"""Get the status of a token and return data.
|
|
28
28
|
|
|
@@ -42,7 +42,7 @@ class TimedUrlSerializer:
|
|
|
42
42
|
try:
|
|
43
43
|
data = s.loads(token, max_age=max_age)
|
|
44
44
|
except SignatureExpired:
|
|
45
|
-
_, data = s.loads_unsafe(token)
|
|
45
|
+
# _, data = s.loads_unsafe(token)
|
|
46
46
|
expired = True
|
|
47
47
|
except (BadSignature, TypeError, ValueError):
|
|
48
48
|
invalid = True
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import secrets
|
|
2
|
+
import string
|
|
3
|
+
import pyotp
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TwoFactorAuthentication:
|
|
7
|
+
def __init__(self, app=None):
|
|
8
|
+
self.app = app
|
|
9
|
+
|
|
10
|
+
def generate_totp_secret(self):
|
|
11
|
+
return pyotp.random_base32()
|
|
12
|
+
|
|
13
|
+
def generate_recovery_codes(self, count=10, length=16):
|
|
14
|
+
alphabet = string.ascii_letters + string.digits
|
|
15
|
+
codes = set()
|
|
16
|
+
while len(codes) < count:
|
|
17
|
+
code = "".join(secrets.choice(alphabet) for _ in range(length))
|
|
18
|
+
codes.add(code)
|
|
19
|
+
return list(codes)
|
|
20
|
+
|
|
21
|
+
def get_totp_code(self, otp_secret):
|
|
22
|
+
if otp_secret is None:
|
|
23
|
+
return None
|
|
24
|
+
otp = pyotp.TOTP(otp_secret)
|
|
25
|
+
return otp.now()
|
|
26
|
+
|
|
27
|
+
def get_totp_uri(self, otp_secret, username):
|
|
28
|
+
otp = pyotp.TOTP(otp_secret)
|
|
29
|
+
if self.app and self.app.config.get("APP_NAME"):
|
|
30
|
+
app_name = self.app.config.get("APP_NAME")
|
|
31
|
+
else:
|
|
32
|
+
app_name = "UnknownApp"
|
|
33
|
+
uri = otp.provisioning_uri(name=username, issuer_name=app_name)
|
|
34
|
+
return uri
|
|
35
|
+
|
|
36
|
+
def verify_totp(self, otp_secret, token):
|
|
37
|
+
otp = pyotp.TOTP(otp_secret)
|
|
38
|
+
return otp.verify(token)
|
|
@@ -3,6 +3,6 @@ $('#fa_modal_window').on('show.bs.modal', function (event) {
|
|
|
3
3
|
let modal = $(this)
|
|
4
4
|
modal.find('.modal-content').load(relatedTarget.attr('href'), function () {
|
|
5
5
|
// window.faForm.applyGlobalStyles(document.getElementsByClassName('modal-content'));
|
|
6
|
-
window.faForm.applyGlobalStyles(modal.find('.modal-content'), true);
|
|
6
|
+
// window.faForm.applyGlobalStyles(modal.find('.modal-content'), true);
|
|
7
7
|
})
|
|
8
8
|
})
|