From 757a2549a9f5ad0ed51a07c4e555050e433da889 Mon Sep 17 00:00:00 2001 From: Johannes Maron Date: Wed, 17 Dec 2025 15:55:21 +0100 Subject: [PATCH] Add image to readme --- .github/workflows/ci.yml | 5 +- .gitignore | 3 +- .pre-commit-config.yaml | 1 - .readthedocs.yaml | 8 +-- README.md | 112 ++++++++++++++++++++++++++++++ README.rst | 124 --------------------------------- docs/conf.py | 48 ------------- docs/contributing.md | 27 ++++++++ docs/contributing.rst | 1 - docs/customizing.md | 117 +++++++++++++++++++++++++++++++ docs/customizing.rst | 130 ----------------------------------- docs/index.md | 1 + docs/index.rst | 16 ----- docs/privacy.md | 36 ++++++++++ docs/privacy.rst | 40 ----------- docs/settings.md | 33 +++++++++ docs/settings.rst | 42 ----------- docs/spelling_wordlist.txt | 17 ----- docs/templates.md | 55 +++++++++++++++ docs/templates.rst | 61 ---------------- docs/usage.md | 0 docs/usage.rst | 1 - images/logo-dark.svg | 13 ++++ images/logo-light.svg | 13 ++++ mailauth/contrib/__init__.py | 1 + mailauth/forms.py | 39 +++++++---- mkdocs.yml | 24 +++++++ pyproject.toml | 8 ++- 28 files changed, 470 insertions(+), 506 deletions(-) create mode 100644 README.md delete mode 100644 README.rst delete mode 100644 docs/conf.py create mode 100644 docs/contributing.md delete mode 100644 docs/contributing.rst create mode 100644 docs/customizing.md delete mode 100644 docs/customizing.rst create mode 120000 docs/index.md delete mode 100644 docs/index.rst create mode 100644 docs/privacy.md delete mode 100644 docs/privacy.rst create mode 100644 docs/settings.md delete mode 100644 docs/settings.rst delete mode 100644 docs/spelling_wordlist.txt create mode 100644 docs/templates.md delete mode 100644 docs/templates.rst create mode 100644 docs/usage.md delete mode 100644 docs/usage.rst create mode 100644 images/logo-dark.svg create mode 100644 images/logo-light.svg create mode 100644 mkdocs.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 601acd4..c774766 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,10 +27,9 @@ jobs: - uses: actions/setup-python@v6 with: python-version: "3.11" - - run: sudo apt-get update && sudo apt-get install -y gettext python3-enchant - - run: python -m pip install sphinxcontrib-spelling + - run: sudo apt-get update && sudo apt-get install -y gettext - run: python -m pip install -e '.[docs]' - - run: python -m sphinx -W -b spelling docs docs/_build + - run: python -m mkdocs build --strict SQLite: needs: [dist, docs] runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 36f407f..af0b24b 100644 --- a/.gitignore +++ b/.gitignore @@ -57,7 +57,8 @@ docs/_build/ # PyBuilder target/ - +# mkdocs documentation +/site/ tests/local.py docs/_build/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 21b0e04..2a9f128 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,7 +26,6 @@ repos: hooks: - id: mdformat additional_dependencies: - - mdformat-ruff - mdformat-ruff - mdformat-footnote - mdformat-gfm diff --git a/.readthedocs.yaml b/.readthedocs.yaml index c49bf5f..61e3278 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -3,11 +3,11 @@ # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details version: 2 build: - os: ubuntu-20.04 + os: ubuntu-24.04 tools: - python: "3.11" -sphinx: - configuration: docs/conf.py + python: "3" +mkdocs: + configuration: mkdocs.yml python: install: - method: pip diff --git a/README.md b/README.md new file mode 100644 index 0000000..0f5df4f --- /dev/null +++ b/README.md @@ -0,0 +1,112 @@ +

+ + + + Django MailAuth: Secure login links; no passwords required! + +

+ +# Django Mail Auth + +[![version](https://img.shields.io/pypi/v/django-mail-auth.svg)](https://pypi.python.org/pypi/django-mail-auth/) +[![Documentation Status](https://readthedocs.org/projects/django-mail-auth/badge/?version=latest)](https://django-mail-auth.readthedocs.io/en/latest/?badge=latest) +[![coverage](https://codecov.io/gh/codingjoe/django-mail-auth/branch/main/graph/badge.svg)](https://codecov.io/gh/codingjoe/django-mail-auth) +[![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/codingjoe/django-mail-auth/main/LICENSE) + +Django Mail Auth is a lightweight authentication backend for Django, +that does not require users to remember passwords. + +Django Mail Auth features: + +- custom user model support +- drop in Django admin support +- drop in Django User replacement +- drop in Wagtail login replacement +- extendable SMS support + +![](sample.png){alt="screenshot from a login form" width="425px"} + +This project was inspired by: + +- [Is it time for password-less login?](http://notes.xoxco.com/post/27999787765/is-it-time-for-password-less-login) + by [Ben Brown](http://twitter.com/benbrown) +- [LOGIN WITHOUT PASSWORD MOST SECURE | WAIT.. WHAT?](https://www.lucius.digital/en/blog/login-without-password-most-secure-wait-what) + by [Joris Snoek](https://twitter.com/lucius_digital) +- [django-nopassword](https://github.com/relekang/django-nopassword) by + [Rolf Erik Lekang](http://rolflekang.com) + +## Installation + +Run this command to install `django-mail-auth`: + +``` +python3 -m pip install django-mail-auth[wagtail] +``` + +## Setup + +First add `mailauth` to you installed apps: + +```python +INSTALLED_APPS = [ + # Django's builtin apps… + "mailauth", + "mailauth.contrib.admin", # optional + "mailauth.contrib.user", # optional + # optional, must be included before "wagtail.admin" + "mailauth.contrib.wagtail", + # other apps… +] +``` + +`mailauth.contrib.admin` is optional and will replace the admin's login +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: + +```python +# This setting should be either "EmailUser" or +# any custom subclass of "AbstractEmailUser" +AUTH_USER_MODEL = "mailauth_user.EmailUser" + +# optional, Wagtail only +WAGTAILUSERS_PASSWORD_ENABLED = False +``` + +Next you will need to add the new authentication backend: + +```python +AUTHENTICATION_BACKENDS = ( + # default, but now optional + # This should be removed if you use mailauth.contrib.user or any other + # custom user model that does not have a username/password + "django.contrib.auth.backends.ModelBackend", + # The new access token based authentication backend + "mailauth.backends.MailAuthBackend", +) +``` + +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 configuration `urls.py` and add +the following: + +```python +from django.urls import path + + +urlpatterns = [ + path("accounts/", include("mailauth.urls")), + # optional, must be before "wagtail.admin.urls" + path("", include("mailauth.contrib.wagtail.urls")), +] +``` + +That's it! + +> [!IMPORTANT] +> Don't forget to setup you Email backend! diff --git a/README.rst b/README.rst deleted file mode 100644 index fba8e4b..0000000 --- a/README.rst +++ /dev/null @@ -1,124 +0,0 @@ -================ -Django Mail Auth -================ - -|version| |docs| |coverage| |license| - -.. figure:: sample.png - :width: 425 - :alt: screenshot from a login form - -Django Mail Auth is a lightweight authentication backend for Django, -that does not require users to remember passwords. - -Django Mail Auth features: - -- custom user model support -- drop in Django admin support -- drop in Django User replacement -- drop in Wagtail login replacement -- extendable SMS support - -This project was inspired by: - -- `Is it time for password-less login?`_ by `Ben Brown`_ -- `LOGIN WITHOUT PASSWORD MOST SECURE | WAIT.. WHAT?`_ by `Joris Snoek`_ -- `django-nopassword`_ by `Rolf Erik Lekang`_ - - -.. _`Rolf Erik Lekang`: http://rolflekang.com -.. _`django-nopassword`: https://github.com/relekang/django-nopassword -.. _`Is it time for password-less login?`: http://notes.xoxco.com/post/27999787765/is-it-time-for-password-less-login -.. _`LOGIN WITHOUT PASSWORD MOST SECURE | WAIT.. WHAT?`: https://www.lucius.digital/en/blog/login-without-password-most-secure-wait-what -.. _`Ben Brown`: http://twitter.com/benbrown -.. _`Joris Snoek`: https://twitter.com/lucius_digital - -Installation ------------- - -Run this command to install ``django-mail-auth``:: - - python3 -m pip install django-mail-auth[wagtail] - -Setup ------ - -First add ``mailauth`` to you installed apps: - -.. code-block:: python - - INSTALLED_APPS = [ - # Django's builtin apps… - - 'mailauth', - - 'mailauth.contrib.admin', # optional - 'mailauth.contrib.user', # optional - - # optional, must be included before "wagtail.admin" - 'mailauth.contrib.wagtail', - - - # other apps… - ] - -``mailauth.contrib.admin`` is optional and will replace the admin's login -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: - -.. code-block:: python - - # This setting should be either "EmailUser" or - # any custom subclass of "AbstractEmailUser" - AUTH_USER_MODEL = 'mailauth_user.EmailUser' - - # optional, Wagtail only - WAGTAILUSERS_PASSWORD_ENABLED = False - - -Next you will need to add the new authentication backend: - -.. code-block:: python - - AUTHENTICATION_BACKENDS = ( - # default, but now optional - # This should be removed if you use mailauth.contrib.user or any other - # custom user model that does not have a username/password - 'django.contrib.auth.backends.ModelBackend', - - # The new access token based authentication backend - 'mailauth.backends.MailAuthBackend', - ) - -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 configuration ``urls.py`` and add the following: - -.. code-block:: python - - from django.urls import path - - - urlpatterns = [ - path('accounts/', include('mailauth.urls')), - - # optional, must be before "wagtail.admin.urls" - path('', include('mailauth.contrib.wagtail.urls')), - ] - -That's it! - -.. note:: Don't forget to setup you Email backend! - -.. |version| image:: https://img.shields.io/pypi/v/django-mail-auth.svg - :target: https://pypi.python.org/pypi/django-mail-auth/ -.. |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/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/docs/conf.py b/docs/conf.py deleted file mode 100644 index d969cc2..0000000 --- a/docs/conf.py +++ /dev/null @@ -1,48 +0,0 @@ -"""Sphinx configuration file.""" - -import os -import sys - -import django -from pkg_resources import get_distribution - -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tests.testapp.settings") -sys.path.insert(0, os.path.abspath("..")) -django.setup() - -project = "Django Mail Auth" -copyright = "2022, Johannes Maron" -release = get_distribution("django-mail-auth").version -version = ".".join(release.split(".")[:2]) - -master_doc = "index" - -extensions = [ - "sphinx.ext.autodoc", - "sphinx.ext.napoleon", - "sphinx.ext.intersphinx", - "sphinx.ext.doctest", -] - -intersphinx_mapping = { - "python": ("https://docs.python.org/3", None), - "django": ( - "https://docs.djangoproject.com/en/stable/", - "https://docs.djangoproject.com/en/stable/_objects/", - ), -} - -try: - import sphinxcontrib.spelling # noqa: F401 -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/contributing.md b/docs/contributing.md new file mode 100644 index 0000000..d286591 --- /dev/null +++ b/docs/contributing.md @@ -0,0 +1,27 @@ +# Contributing + +To run test suite run: + +```console +uv run pytest +``` + +To build the documentation run: + +``` +uv run mkdocs serve +``` + +## The sample app + +To run a full example --- e.g. to debug frontend code -- you can run: + +``` +uv run tests/manage.py migrate +uv run tests/manage.py createsuperuser +# You will be asked for the email address of your new superuser +uv run tests/manage.py runserver +``` + +Next you can go to and log in with your +newly created superuser. diff --git a/docs/contributing.rst b/docs/contributing.rst deleted file mode 100644 index e582053..0000000 --- a/docs/contributing.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../CONTRIBUTING.rst diff --git a/docs/customizing.md b/docs/customizing.md new file mode 100644 index 0000000..2bc73e9 --- /dev/null +++ b/docs/customizing.md @@ -0,0 +1,117 @@ +# Customizing + +## Custom login message (like SMS) + +Django Mail Auth can be easily extended. Besides template adaptations it +is possible to send different messages like SMS. To make those changes, +you will need to write a custom login form. + +### Custom login form + +Custom login forms need to inherit from +[BaseLoginForm][mailauth.forms.BaseLoginForm] and override the +[save][mailauth.forms.BaseLoginForm.save] method. + +The following example is for a login SMS. This will require a custom +user model with a unique `phone_number` field: + +```python +from django import forms +from django.contrib.auth import get_user_model +from django.template import loader +from mailauth.forms import BaseLoginForm + + +class SmsLoginForm(BaseLoginForm): + phone_number = forms.CharField() + + template_name = 'registration/login_sms.txt + from_number = None + + def __init__(self, *args, **kwargs): + self.twilio_client = TwilioRestClient( + settings.TWILIO_SID, + settings.TWILIO_AUTH_TOKEN + ) + super().__init__(*args, **kwargs) + + def save(self): + phone_number = self.cleaned_data['phone_number'] + user = get_user_model().objects.get( + phone_number=phone_number + ) + context = self.get_context(self.request, user) + + from_number = self.from_number or getattr( + settings, 'DEFAULT_FROM_NUMBER' + ) + sms_content = loader.render_to_string( + self.template_name, context + ) + + self.twilio_client.messages.create( + to=user.phone_number, + from_=from_number, + body=sms_content + ) +``` + +To add the new login form, simply add a new login view to your URL +configuration with the custom form: + +```python +from django.urls import path +from mailauth.views import LoginView + +from .forms import SmsLoginForm + +urlpatterns = [ + path("login/sms/", LoginView.as_view(form_class=SmsLoginForm), name="login-sms"), +] +``` + +### API documentation + +:::mailauth.forms.BaseLoginForm + +## Custom User Model + +For convenience, Django Mail Auth provides a +[EmailUser][mailauth.contrib.user.models.EmailUser] which is almost identical to Django's built-in +[User][django.contrib.auth.models.User] but without the +[password][django.contrib.auth.models.User.password] and +[username][django.contrib.auth.models.User.username] field. The +[email][mailauth.contrib.user.models.AbstractEmailUser.email] field serves as a username and is -- different to Django's +User -- unique and case-insensitive. + +### Implementing a custom User model + +```python +from mailauth.contrib.user.models import AbstractEmailUser +from phonenumber_field.modelfields import PhoneNumberField + + +class SMSUser(AbstractEmailUser): + phone_number = phone = PhoneNumberField( + _("phone number"), unique=True, db_index=True + ) + + +class Meta(AbstractEmailUser.Meta): + verbose_name = _("user") + verbose_name_plural = _("users") + swappable = "AUTH_USER_MODEL" +``` + +> [!NOTE] +> Do not forget to adjust your `AUTH_USER_MODEL` to correct `app_label.ModelName`. + +### API documentation + +:::mailauth.contrib.user.models.AbstractEmailUser + +:::mailauth.contrib.user.models.AbstractEmailUser.email + +:::mailauth.contrib.user.models.AbstractEmailUser.session_salt + +:::mailauth.contrib.user.models.EmailUser diff --git a/docs/customizing.rst b/docs/customizing.rst deleted file mode 100644 index bab5776..0000000 --- a/docs/customizing.rst +++ /dev/null @@ -1,130 +0,0 @@ -=========== -Customizing -=========== - -Custom login message (like SMS) -_______________________________ - -Django Mail Auth can be easily extended. Besides template adaptations it is -possible to send different messages like SMS. To make those changes, you -will need to write a custom login form. - -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. 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 - from django.template import loader - from mailauth.forms import BaseLoginForm - - - class SmsLoginForm(BaseLoginForm): - phone_number = forms.CharField() - - template_name = 'registration/login_sms.txt - from_number = None - - def __init__(self, *args, **kwargs): - self.twilio_client = TwilioRestClient( - settings.TWILIO_SID, - settings.TWILIO_AUTH_TOKEN - ) - super().__init__(*args, **kwargs) - - def save(self): - phone_number = self.cleaned_data['phone_number'] - user = get_user_model().objects.get( - phone_number=phone_number - ) - context = self.get_context(self.request, user) - - from_number = self.from_number or getattr( - settings, 'DEFAULT_FROM_NUMBER' - ) - sms_content = loader.render_to_string( - self.template_name, context - ) - - self.twilio_client.messages.create( - to=user.phone_number, - from_=from_number, - body=sms_content - ) - - -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 - - from .forms import SmsLoginForm - - urlpatterns = [ - path( - 'login/sms/', - LoginView.as_view(form_class=SmsLoginForm), - name='login-sms' - ), - ] - -API documentation ------------------ - -.. autoclass:: mailauth.forms.BaseLoginForm - :members: - -Custom User Model -_________________ - -For convenience, Django Mail Auth provides a -:class:`EmailUser` which is almost -identical to Django's built in :class:`User` -but without the :attr:`password` -and :attr:`username` field. -The :attr:`email` -field serves as a username and is – different to Django's User – -unique and case insensitive. - -Implementing a custom User model --------------------------------- - -.. code-block:: python - - from mailauth.contrib.user.models import AbstractEmailUser - from phonenumber_field.modelfields import PhoneNumberField - - - class SMSUser(AbstractEmailUser): - phone_number = phone = PhoneNumberField(_("phone number"), unique=True, db_index=True) - - class Meta(AbstractEmailUser.Meta): - verbose_name = _("user") - verbose_name_plural = _("users") - swappable = "AUTH_USER_MODEL" - -.. note:: Do not forget to adjust your ``AUTH_USER_MODEL`` to correct ``app_label.ModelName``. - -API documentation ------------------ - -.. autoclass:: mailauth.contrib.user.models.AbstractEmailUser - :members: - - .. autoattribute:: mailauth.contrib.user.models.AbstractEmailUser.email - :noindex: - .. autoattribute:: mailauth.contrib.user.models.AbstractEmailUser.session_salt - :noindex: - -.. autoclass:: mailauth.contrib.user.models.EmailUser - :members: diff --git a/docs/index.md b/docs/index.md new file mode 120000 index 0000000..32d46ee --- /dev/null +++ b/docs/index.md @@ -0,0 +1 @@ +../README.md \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index 0a08e0a..0000000 --- a/docs/index.rst +++ /dev/null @@ -1,16 +0,0 @@ -.. include:: ../README.rst - -All Contents -============ - -.. toctree:: - :maxdepth: 2 - :caption: Contents: - :glob: - - usage - templates - privacy - customizing - settings - contributing diff --git a/docs/privacy.md b/docs/privacy.md new file mode 100644 index 0000000..f363f28 --- /dev/null +++ b/docs/privacy.md @@ -0,0 +1,36 @@ +# Privacy + +## Anonymization + +User privacy is important, not only to meet local regulations, but also +to protect your users and allow them to exercise their rights. However, +it's not always practical to delete users, especially if they have +dependent objects, that are relevant for statistical analysis. + +Anonymization is a process of removing the user's personal data whilst +keeping related data intact. This is done by using the `anomymize` +method. + +::: mailauth.contrib.user.models.AbstractEmailUser.anonymize + +This method may be overwritten to provide anonymization for you custom +user model. + +Related objects may also listen to the anonymize signal. + +::: mailauth.contrib.user.signals.anonymize + +All those methods can be conveniently triggered via the `anonymize` +admin action. + +::: mailauth.contrib.user.admin.AnonymizableAdminMixin + +## Liability Waiver + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/docs/privacy.rst b/docs/privacy.rst deleted file mode 100644 index e180d4b..0000000 --- a/docs/privacy.rst +++ /dev/null @@ -1,40 +0,0 @@ -Privacy -======== - -Anonymization -------------- - -User privacy is important, not only to meet local regulations, but also to -protect your users and allow them to exercise their rights. However, -it's not always practical to delete users, especially if they have dependent -objects, that are relevant for statistical analysis. - -Anonymization is a process of removing the user's personal data whilst keeping -related data intact. This is done by using the ``anomymize`` method. - - - -.. automethod:: mailauth.contrib.user.models.AbstractEmailUser.anonymize - :noindex: - -This method may be overwritten to provide anonymization for you custom user model. - -Related objects may also listen to the anonymize signal. - -.. autoclass:: mailauth.contrib.user.signals.anonymize - -All those methods can be conveniently triggered via the ``anonymize`` admin action. - -.. autoclass:: mailauth.contrib.user.admin.AnonymizableAdminMixin - :members: - -Liability Waiver ----------------- - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/docs/settings.md b/docs/settings.md new file mode 100644 index 0000000..3023430 --- /dev/null +++ b/docs/settings.md @@ -0,0 +1,33 @@ +# Settings + +## Mail Auth settings + +`LOGIN_URL_TIMEOUT` Default: `900` + +Defines how long a login code is valid in seconds. + +`LOGIN_REQUESTED_URL` Default: `accounts/login/success` + +Defines the URL the user will be redirected to, after requesting an +authentication message. + +`LOGIN_TOKEN_SINGLE_USE` Default: `True` + +Defines if a token can be used more than once. If `True`, the same token +can only be used once and will be invalid the next try. If `False`, the +same token can be used multiple times and remains valid until expired. + +## Django related settings + +`DEFAULT_FROM_EMAIL` Default: `'root@example.com'` + +The sender email address for authentication emails send by Django Mail +Auth. + +`SECRET_KEY` + +> [!WARNING] +> *Keep it secret, keep it safe!* +> +> This key is the foundation of all of Django security measures and for +> this package. diff --git a/docs/settings.rst b/docs/settings.rst deleted file mode 100644 index 670be58..0000000 --- a/docs/settings.rst +++ /dev/null @@ -1,42 +0,0 @@ -Settings -======== - -Mail Auth settings ------------------- - -.. attribute:: LOGIN_URL_TIMEOUT - - Default: ``900`` - - Defines how long a login code is valid in seconds. - -.. attribute:: LOGIN_REQUESTED_URL - - Default: ``accounts/login/success`` - - Defines the URL the user will be redirected to, after requesting an - authentication message. - -.. attribute:: LOGIN_TOKEN_SINGLE_USE - - Default: ``True`` - - Defines if a token can be used more than once. - If ``True``, the same token can only be used once and will be invalid the next try. - If ``False``, the same token can be used multiple times and remains valid until expired. - -Django related settings ------------------------ - -.. attribute:: DEFAULT_FROM_EMAIL - - Default: ``'root@example.com'`` - - The sender email address for authentication emails send by Django Mail Auth. - -.. attribute:: SECRET_KEY - - .. attention:: *Keep it secret, keep it safe!* - - This key is the foundation of all of Django security measures and for - this package. diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt deleted file mode 100644 index 7cc653a..0000000 --- a/docs/spelling_wordlist.txt +++ /dev/null @@ -1,17 +0,0 @@ -admin -anonymize -anonymized -anonymization -Auth -boolean -backend -Django -frontend -mixin -mixins -namespace -subclasses -tuple -tuples -triple -URL diff --git a/docs/templates.md b/docs/templates.md new file mode 100644 index 0000000..6c9687d --- /dev/null +++ b/docs/templates.md @@ -0,0 +1,55 @@ +# Templates + +There are a couple relevant templates, that can be overridden to your +needs. + +## Mail Auth templates + +### Login templates + +`registration/login_requested.html` + +This template will be displayed after a user successfully requested a +login URL. This template is not proved by the package and needs to be +created. + +### Email templates + +`registration/login_subject.txt` + +This template defines the subject line of the email that will be sent to +the user. + +This template is provided by the package and can be overridden. + +`registration/login_email.txt` + +This is the plain text template for the email containing the +authentication URL that will be sent to the user. + +This template is provided by the package and can be overridden. + +`registration/login_email.html` + +This is the HTML template for the email containing the authentication +URL that will be sent to the user. + +This template is optional. If not provided, only plain text emails will +be sent. + +## Django related templates + +Mail Auth uses Django's default templates for the login views. + +### Login templates + +`registration/login.html` + +This template displays login form, where a user can request a login URL. +This template is not proved Django or by the package and needs to be +created. + +`registration/logged_out.html` + +This template will be displayed after a successful logout. This template +is not proved Django or by the package and needs to be created. diff --git a/docs/templates.rst b/docs/templates.rst deleted file mode 100644 index 5554a2b..0000000 --- a/docs/templates.rst +++ /dev/null @@ -1,61 +0,0 @@ -========= -Templates -========= - -There are a couple relevant templates, that can be overridden to your needs. - -Mail Auth templates -------------------- - -Login templates -~~~~~~~~~~~~~~~ - -.. attribute:: registration/login_requested.html - -This template will be displayed after a user successfully requested a login -URL. This template is not proved by the package and needs to be created. - -Email templates -~~~~~~~~~~~~~~~ - -.. attribute:: registration/login_subject.txt - -This template defines the subject line of the email that will be sent to -the user. - -This template is provided by the package and can be overridden. - -.. attribute:: registration/login_email.txt - -This is the plain text template for the email containing the authentication -URL that will be sent to the user. - -This template is provided by the package and can be overridden. - -.. attribute:: registration/login_email.html - -This is the HTML template for the email containing the authentication URL that -will be sent to the user. - - -This template is optional. If not provided, only plain text emails will be -sent. - - -Django related templates ------------------------- - -Mail Auth uses Django's default templates for the login views. - -Login templates -~~~~~~~~~~~~~~~ - -.. attribute:: registration/login.html - -This template displays login form, where a user can request a login URL. This -template is not proved Django or by the package and needs to be created. - -.. attribute:: registration/logged_out.html - -This template will be displayed after a successful logout. This template is -not proved Django or by the package and needs to be created. diff --git a/docs/usage.md b/docs/usage.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/usage.rst b/docs/usage.rst deleted file mode 100644 index 72a3355..0000000 --- a/docs/usage.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../README.rst diff --git a/images/logo-dark.svg b/images/logo-dark.svg new file mode 100644 index 0000000..00946bd --- /dev/null +++ b/images/logo-dark.svg @@ -0,0 +1,13 @@ + + + + + Django + + + MailAuth + + + Secure login links; no passwords required! + + diff --git a/images/logo-light.svg b/images/logo-light.svg new file mode 100644 index 0000000..d34009d --- /dev/null +++ b/images/logo-light.svg @@ -0,0 +1,13 @@ + + + + + Django + + + MailAuth + + + Secure login links; no passwords required! + + diff --git a/mailauth/contrib/__init__.py b/mailauth/contrib/__init__.py index e69de29..c511d76 100644 --- a/mailauth/contrib/__init__.py +++ b/mailauth/contrib/__init__.py @@ -0,0 +1 @@ +"""Secure login links; no passwords required.""" diff --git a/mailauth/forms.py b/mailauth/forms.py index 7f3198c..6eeb617 100644 --- a/mailauth/forms.py +++ b/mailauth/forms.py @@ -1,5 +1,9 @@ +from __future__ import annotations + +import typing import urllib +import django from django import forms from django.contrib.auth import get_user_model from django.contrib.sites.shortcuts import get_current_site @@ -14,17 +18,19 @@ class BaseLoginForm(forms.Form): next = forms.CharField(widget=forms.HiddenInput, required=False) - def get_login_url(self, request, token, next=None): + def get_login_url( + self, request: django.http.request.HttpRequest, token: str, next: str = None + ) -> str: """ Return user login URL including the access token. Args: - request (django.http.request.HttpRequest): Current request. - token (str): The user specific authentication token. - next (str): The path the user should be forwarded to after login. + request: Current request. + token: The user specific authentication token. + next: The path the user should be forwarded to after login. Returns: - str: User login URL including the access token. + User login URL including the access token. """ protocol = "https" if request.is_secure() else "http" @@ -38,26 +44,29 @@ def get_login_url(self, request, token, next=None): url += f"?next={urllib.parse.quote(next)}" return url - def get_token(self, user): + def get_token(self, user: django.contrib.auth.base_user.AbstractBaseUser) -> str: """Return the access token.""" return MailAuthBackend.get_token(user=user) - def get_mail_context(self, request, user): + def get_mail_context( + self, + request: django.http.request.HttpRequest, + user: django.contrib.auth.base_user.AbstractBaseUser, + ) -> dict[str, typing.Any]: """ Return the context for a message template render. Args: - request (django.http.request.HttpRequest): Current request. + request: Current request. user: The user requesting a login message. Returns: - dict: - A context dictionary including: - - ``site`` - - ``site_name`` - - ``token`` - - ``login_url`` - - ``user`` + A context dictionary including: + - ``site`` + - ``site_name`` + - ``token`` + - ``login_url`` + - ``user`` """ token = self.get_token(user) diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..692582a --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,24 @@ +site_name: Django MailAuth +site_url: https://django-mailauth.codingjoe.dev +repo_url: https://github.com/codingjoe/django-mail-auth +plugins: + - autorefs + - search + - mkdocstrings: + default_handler: python + handlers: + python: + inventories: + - https://docs.python.org/3/objects.inv + - https://docs.djangoproject.com/en/stable/objects.inv +theme: + name: material + features: + - search.suggest + - search.share + - content.code.select +markdown_extensions: + - github-callouts + - pymdownx.highlight: + use_pygments: true + - pymdownx.superfences diff --git a/pyproject.toml b/pyproject.toml index cecb461..a5d5bba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ name = "django-mail-auth" authors = [ { name = "Johannes Maron", email = "johannes@maron.family" }, ] -readme = "README.rst" +readme = "README.md" license = { file = "LICENSE" } keywords = [ "django", @@ -54,7 +54,11 @@ test = [ "pytest-django", ] docs = [ - "sphinx", + "mkdocs", + "mkdocstrings[python]>=0.18", + "mkdocs-material", + "markdown-callouts", + "Pygments", ] wagtail = [ "wagtail>=6.3",