diff --git a/.editorconfig b/.editorconfig index 1b25a67..70b2613 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,7 +10,7 @@ insert_final_newline = true charset = utf-8 end_of_line = lf -[*.{json,yml,yaml,js,jsx}] +[*.{json,yml,yaml,js,jsx,toml}] indent_size = 2 [*.{html,htm}] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6085a4a..9399246 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,36 +13,32 @@ jobs: fail-fast: false matrix: lint-command: - - "bandit -r mailauth -x tests" - - "black --check --diff ." - - "flake8 ." - - "isort --check-only --diff ." - - "pydocstyle ." + - bandit -r . -x ./tests + - black --check --diff . + - flake8 . + - isort --check-only --diff . + - pydocstyle . runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: - python-version: "3.10" - cache: 'pip' - cache-dependency-path: 'requirements.txt' - - run: python -m pip install -r requirements.txt + python-version: "3.x" + cache: pip + cache-dependency-path: linter-requirements.txt + - run: python -m pip install -r linter-requirements.txt - run: ${{ matrix.lint-command }} dist: runs-on: ubuntu-latest steps: - - run: sudo apt install -y gettext + - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: - python-version: "3.10" - - uses: actions/setup-node@v3.5.1 - with: - node-version: 'lts/*' - - uses: actions/checkout@v3 + python-version: "3.x" - name: Install Python dependencies - run: python -m pip install --upgrade pip setuptools wheel twine readme-renderer - - run: python setup.py sdist bdist_wheel + run: python -m pip install --upgrade pip build wheel twine readme-renderer + - run: python -m build --sdist --wheel - run: python -m twine check dist/* - uses: actions/upload-artifact@v3 with: @@ -54,68 +50,64 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: - python-version: "3.10" - cache: 'pip' - cache-dependency-path: 'requirements.txt' - - run: python -m pip install --upgrade pip setuptools wheel - - run: python -m pip install -r requirements.txt - - run: python setup.py develop - - run: python setup.py build_sphinx -W + python-version: "3.11" + - run: sudo apt install -y python3-enchant + - run: python -m pip install sphinxcontrib-spelling + - run: python -m pip install -e '.[docs]' + - run: python -m sphinx -W -b spelling docs docs/_build SQLite: - needs: [lint, dist, docs] + needs: [ lint, dist, docs ] runs-on: ubuntu-latest strategy: matrix: python-version: - - "3.8" - "3.9" - "3.10" + - "3.11" steps: - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - - run: python -m pip install --upgrade pip setuptools wheel - - uses: actions/checkout@v3 - - run: python setup.py test - - name: Codecov - run: | - python -m pip install codecov - codecov + - run: python -m pip install -e '.[test]' + - run: python -m pytest + - uses: codecov/codecov-action@v2 + with: + flags: ${{ matrix.python-version }} - extras: - needs: [lint, dist, docs] + contrib: + needs: [ lint, dist, docs ] runs-on: ubuntu-latest strategy: matrix: extras: - wagtail - python-version: ["3.10"] + python-version: [ "3.x" ] steps: + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - - run: python -m pip install --upgrade pip setuptools wheel - - uses: actions/checkout@v3 - - run: python -m pip install -e ".[${{ matrix.extras }}]" - - run: python setup.py test - - name: Codecov - run: | - python -m pip install codecov - codecov + - run: python -m pip install -e ".[test,${{ matrix.extras }}]" + - run: python -m pytest + - uses: codecov/codecov-action@v2 + with: + flags: ${{ matrix.extras }} PostgreSQL: - needs: [lint, dist, docs] + needs: [ lint, dist, docs ] runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.10"] + python-version: [ "3.10" ] django-version: - "4.0" - "4.1" + extras: + - postgres services: postgres: image: postgres @@ -130,14 +122,40 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - - run: python -m pip install --upgrade pip setuptools wheel - uses: actions/checkout@v3 - - run: python -m pip install "psycopg2-binary<2.9" Django~=${{ matrix.django-version }}.0 - - run: python setup.py test + - run: python -m pip install Django~=${{ matrix.django-version }}.0 -e ".[test,${{ matrix.extras }}]" + - run: python -m pytest env: DB_PORT: ${{ job.services.postgres.ports[5432] }} DB: pg - - name: Codecov - run: | - python -m pip install codecov - codecov + - uses: codecov/codecov-action@v2 + with: + flags: ${{ matrix.extras }} + + analyze: + name: CodeQL + needs: [ SQLite, contrib, PostgreSQL ] + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + strategy: + fail-fast: false + matrix: + language: [ python ] + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + queries: +security-and-quality + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + if: ${{ matrix.language == 'javascript' || matrix.language == 'python' }} + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{ matrix.language }}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 70c84f1..d603a29 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,13 +12,10 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: - python-version: "3.10" - - name: Install Python dependencies - run: python -m pip install --upgrade pip setuptools wheel twine - - name: Build dist packages - run: python setup.py sdist bdist_wheel - - name: Upload packages - run: python -m twine upload dist/* + python-version: "3.x" + - run: python -m pip install --upgrade pip build wheel twine + - run: python -m build --sdist --wheel + - run: python -m twine upload dist/* env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} diff --git a/.gitignore b/.gitignore index 91d7f47..36f407f 100644 --- a/.gitignore +++ b/.gitignore @@ -63,3 +63,4 @@ tests/local.py docs/_build/ venv .python-version +_version.py diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..bb73bc5 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,20 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +version: 2 + +build: + os: ubuntu-20.04 + tools: + python: "3.11" + +sphinx: + configuration: docs/conf.py + +python: + install: + - method: pip + path: . + extra_requirements: + - docs diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 63393b9..9530e0c 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -4,29 +4,22 @@ Contributing To install the development requirements simply run:: - python setup.py develop + python -m pip install -e '.[test]' To run test suite run:: - python setup.py test - -... and to run the entire test suite, simply use tox:: - - pip install --upgrade tox - tox + python -m pytest To build the documentation run:: - python setup.py build_sphinx - open docs/_build/html/index.html - + python -m sphinx -W -b spelling docs docs/_build The sample app ============== To run a full example — e.g. to debug frontend code – you can run:: - python setup.py develop + python -m pip install -e . python tests/testapp/manage.py migrate python tests/testapp/manage.py createsuperuser # You will be asked for the email address of your new superuser diff --git a/README.rst b/README.rst index e7815ba..6ed987e 100644 --- a/README.rst +++ b/README.rst @@ -43,7 +43,9 @@ Run this command to install ``django-mail-auth``:: Setup ----- -First add ``mailauth`` to you installed apps:: +First add ``mailauth`` to you installed apps: + +.. code-block:: python INSTALLED_APPS = [ # Django's builtin apps… @@ -64,7 +66,9 @@ First add ``mailauth`` to you installed apps:: with token based authentication too. ``mailauth.contrib.user`` is optional and provides a new Django User model. -The new User model needs to be enabled via the ``AUTH_USER_MODEL`` setting:: +The new User model needs to be enabled via the ``AUTH_USER_MODEL`` setting: + +.. code-block:: python # This setting should be either "EmailUser" or # any custom subclass of "AbstractEmailUser" @@ -74,7 +78,9 @@ The new User model needs to be enabled via the ``AUTH_USER_MODEL`` setting:: WAGTAILUSERS_PASSWORD_ENABLED = False -Next you will need to add the new authentication backend:: +Next you will need to add the new authentication backend: + +.. code-block:: python AUTHENTICATION_BACKENDS = ( # default, but now optional @@ -89,7 +95,9 @@ Next you will need to add the new authentication backend:: Django's ``ModelBackend`` is only needed, if you still want to support password based authentication. If you don't, simply remove it from the list. -Last but not least, go to your URL root config ``urls.py`` and add the following:: +Last but not least, go to your URL root configuration ``urls.py`` and add the following: + +.. code-block:: python from django.urls import path @@ -107,12 +115,12 @@ That's it! .. |version| image:: https://img.shields.io/pypi/v/django-mail-auth.svg :target: https://pypi.python.org/pypi/django-mail-auth/ -.. |ci| image:: https://travis-ci.com/codingjoe/django-mail-auth.svg?branch=master +.. |ci| image:: https://travis-ci.com/codingjoe/django-mail-auth.svg?branch=main :target: https://travis-ci.com/codingjoe/django-mail-auth -.. |coverage| image:: https://codecov.io/gh/codingjoe/django-mail-auth/branch/master/graph/badge.svg +.. |coverage| image:: https://codecov.io/gh/codingjoe/django-mail-auth/branch/main/graph/badge.svg :target: https://codecov.io/gh/codingjoe/django-mail-auth .. |license| image:: https://img.shields.io/badge/license-MIT-blue.svg - :target: :target: https://raw.githubusercontent.com/codingjoe/django-mail-auth/master/LICENSE + :target: :target: https://raw.githubusercontent.com/codingjoe/django-mail-auth/main/LICENSE .. |docs| image:: https://readthedocs.org/projects/django-mail-auth/badge/?version=latest :target: https://django-mail-auth.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..f9dc270 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,7 @@ +# Security Policy + +## Security contact information + +To report a security vulnerability, please use the +[Tidelift security contact](https://tidelift.com/security). +Tidelift will coordinate the fix and disclosure. diff --git a/docs/conf.py b/docs/conf.py index 6308f5a..4654c9c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -10,7 +10,7 @@ django.setup() project = "Django Mail Auth" -copyright = "2019, Johannes Hoppe" +copyright = "2022, Johannes Maron" release = get_distribution("django-mail-auth").version version = ".".join(release.split(".")[:2]) @@ -31,6 +31,16 @@ ), } +try: + import sphinxcontrib.spelling # noqa +except ImportError: + pass +else: + extensions.append("sphinxcontrib.spelling") + + spelling_word_list_filename = "spelling_wordlist.txt" + spelling_show_suggestions = True + autodoc_default_options = { "show-inheritance": True, diff --git a/docs/customizing.rst b/docs/customizing.rst index 0feb8ca..bab5776 100644 --- a/docs/customizing.rst +++ b/docs/customizing.rst @@ -15,8 +15,10 @@ Custom login form Custom login forms need to inherit from :class:`.BaseLoginForm` and override the :meth:`save<.BaseLoginForm.save>` method. -The following example is for a login SMS via twilio. This will require a -custom user model with a unique ``phone_number`` field:: +The following example is for a login SMS. This will require a +custom user model with a unique ``phone_number`` field: + +.. code-block:: python from django import forms from django.contrib.auth import get_user_model @@ -58,8 +60,10 @@ custom user model with a unique ``phone_number`` field:: ) -To add the new login form, simply add a new login view to your URL config with -the custom form:: +To add the new login form, simply add a new login view to your URL configuration with +the custom form: + +.. code-block:: python from django.urls import path from mailauth.views import LoginView diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt new file mode 100644 index 0000000..7cc653a --- /dev/null +++ b/docs/spelling_wordlist.txt @@ -0,0 +1,17 @@ +admin +anonymize +anonymized +anonymization +Auth +boolean +backend +Django +frontend +mixin +mixins +namespace +subclasses +tuple +tuples +triple +URL diff --git a/requirements.txt b/linter-requirements.txt similarity index 57% rename from requirements.txt rename to linter-requirements.txt index ecdc843..c3083bc 100644 --- a/requirements.txt +++ b/linter-requirements.txt @@ -1,7 +1,5 @@ -Django>=2.2 -Wagtail>=2.8 bandit==1.7.4 black==22.10.0 flake8==5.0.4 isort==5.10.1 -pydocstyle==6.1.1 +pydocstyle[toml]==6.1.1 diff --git a/mailauth/__init__.py b/mailauth/__init__.py index 8a9b28a..6cf2d89 100644 --- a/mailauth/__init__.py +++ b/mailauth/__init__.py @@ -1 +1,5 @@ -"""Django authentication via login urls, no password required.""" +"""Django authentication via login URLs, no passwords required.""" +from . import _version + +__version__ = _version.version +VERSION = _version.version_tuple diff --git a/mailauth/forms.py b/mailauth/forms.py index d20533f..2216199 100644 --- a/mailauth/forms.py +++ b/mailauth/forms.py @@ -53,12 +53,11 @@ def get_mail_context(self, request, user): Returns: dict: A context dictionary including: - - - site - - site_name - - token - - login_url - - user + - ``site`` + - ``site_name`` + - ``token`` + - ``login_url`` + - ``user`` """ token = self.get_token(user) @@ -79,7 +78,7 @@ def save(self): Called from the login view, if the form is valid. This method must be implemented by subclasses. This method - should trigger the login url to be sent to the user. + should trigger the login URL to be sent to the user. """ raise NotImplementedError @@ -94,7 +93,7 @@ class EmailLoginForm(BaseLoginForm): def __init__(self, request, *args, **kwargs): self.request = request - super(EmailLoginForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.field_name = get_user_model().get_email_field_name() model_field = get_user_model()._meta.get_field(self.field_name) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..0ee7a31 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,98 @@ +[build-system] +requires = ["flit_core>=3.2", "flit_scm", "wheel"] +build-backend = "flit_scm:buildapi" + +[project] +name = "django-mail-auth" +authors = [ + { name = "Johannes Maron", email = "johannes@maron.family" }, +] +readme = "README.rst" +license = { file = "LICENSE" } +keywords = [ + "django", + "otp", + "email", + "authentication", + "login", + "2fa", + "passwordless", + "password", +] +dynamic = ["version", "description"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Environment :: Console", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3 :: Only", + "Framework :: Django", + "Framework :: Django :: 4.0", + "Framework :: Django :: 4.1", + "Topic :: Internet", + "Topic :: Internet :: WWW/HTTP", + "Topic :: Software Development :: Quality Assurance", + "Topic :: Software Development :: Testing", +] +requires-python = ">=3.9" +dependencies = ["django>=4.0"] + +[project.optional-dependencies] +test = [ + "pytest", + "pytest-cov", + "pytest-django", +] +docs = [ + "sphinx", +] +wagtail = [ + "wagtail>=2.8", +] +postgres = [ + "psycopg2-binary", +] + +[project.urls] +Project-URL = "https://github.com/codingjoe/django-mail-auth" +Changelog = "https://github.com/codingjoe/django-mail-auth/releases" +Source = "https://github.com/codingjoe/django-mail-auth" +Documentation = "https://django-mail-auth.rtfd.io/" +Issue-Tracker = "https://github.com/codingjoe/django-mail-auth/issues" + +[tool.flit.module] +name = "mailauth" + +[tool.setuptools_scm] +write_to = "mailauth/_version.py" + +[tool.pytest.ini_options] +minversion = "6.0" +addopts = "--cov --tb=short -rxs" +testpaths = ["tests"] +DJANGO_SETTINGS_MODULE = "tests.testapp.settings" + +[tool.coverage.run] +source = ["mailauth"] + +[tool.coverage.report] +show_missing = true + +[tool.isort] +atomic = true +line_length = 88 +multi_line_output = 3 +force_grid_wrap = 0 +known_first_party = "mailauth, tests" +include_trailing_comma = true +use_parentheses = true +default_section = "THIRDPARTY" +combine_as_imports = true + +[tool.pydocstyle] +add_ignore = "D1" diff --git a/setup.cfg b/setup.cfg index 5ece0a8..6f60592 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,80 +1,4 @@ -[metadata] -name = django-mail-auth -author = Johannes Maron -author_email = johannes@maron.family -description = Django authentication via login URLs, no passwords required -long_description = file: README.rst -url = https://github.com/codingjoe/django-mail-auth -license = MIT -license_file = LICENSE -classifier = - Development Status :: 5 - Production/Stable - License :: OSI Approved :: MIT License - Operating System :: OS Independent - Intended Audience :: Developers - Programming Language :: Python - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 - Programming Language :: Python :: 3 :: Only - Topic :: Internet :: WWW/HTTP - Topic :: Internet - Framework :: Django - Framework :: Django :: 4.0 - Framework :: Django :: 4.1 -keywords = django, otp, password, email - -[options] -include_package_data = True -packages = find: -install_requires = - django>=4.0 -setup_requires = - setuptools_scm - sphinx - pytest-runner -tests_require = - pytest - pytest-django - pytest-cov - -[options.package_data] -* = *.txt, *.rst, *.html, *.po - -[options.extras_require] -wagtail = wagtail>=2.8 - -[options.packages.find] -exclude = - tests - -[bdist_wheel] -universal = 1 - -[aliases] -test = pytest - -[tool:pytest] -addopts = --cov=mailauth --cov-report term-missing --tb=short -DJANGO_SETTINGS_MODULE = tests.testapp.settings - -[build_sphinx] -source-dir = docs -build-dir = docs/_build - [flake8] max-line-length=88 select = C,E,F,W,B,B950 ignore = E203, E501, W503, E731 - -[pydocstyle] -add_ignore = D1 - -[isort] -atomic = true -line_length = 88 -known_first_party = mailauth, tests -include_trailing_comma = True -default_section=THIRDPARTY -combine_as_imports = true diff --git a/setup.py b/setup.py deleted file mode 100755 index 65d03cd..0000000 --- a/setup.py +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env python -from setuptools import setup - -setup(name="django-mail-auth", use_scm_version=True) diff --git a/tests/__init__.py b/tests/__init__.py index d2aca59..e69de29 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1 +0,0 @@ -# -*- coding: utf8 -*-