summaryrefslogtreecommitdiffstats
path: root/users/models
diff options
context:
space:
mode:
authorAndrew Godwin2022-11-17 19:16:34 -0700
committerAndrew Godwin2022-11-17 19:16:34 -0700
commit6adfdbabe0d44c17f32abc9d48a6e252e2a0792e (patch)
tree6644c5eeab7970a9f9b8d9540b7ebe28cc499331 /users/models
parent2a3690d1c148da5dd799052403ba7290e1fb7de0 (diff)
downloadtakahe-6adfdbabe0d44c17f32abc9d48a6e252e2a0792e.tar.gz
takahe-6adfdbabe0d44c17f32abc9d48a6e252e2a0792e.tar.bz2
takahe-6adfdbabe0d44c17f32abc9d48a6e252e2a0792e.zip
Add signup and password reset
Diffstat (limited to 'users/models')
-rw-r--r--users/models/__init__.py1
-rw-r--r--users/models/password_reset.py92
2 files changed, 93 insertions, 0 deletions
diff --git a/users/models/__init__.py b/users/models/__init__.py
index 28d62b0..e46860e 100644
--- a/users/models/__init__.py
+++ b/users/models/__init__.py
@@ -3,5 +3,6 @@ from .domain import Domain # noqa
from .follow import Follow, FollowStates # noqa
from .identity import Identity, IdentityStates # noqa
from .inbox_message import InboxMessage, InboxMessageStates # noqa
+from .password_reset import PasswordReset # noqa
from .user import User # noqa
from .user_event import UserEvent # noqa
diff --git a/users/models/password_reset.py b/users/models/password_reset.py
new file mode 100644
index 0000000..90062d3
--- /dev/null
+++ b/users/models/password_reset.py
@@ -0,0 +1,92 @@
+import random
+import string
+
+from asgiref.sync import sync_to_async
+from django.conf import settings
+from django.core.mail import send_mail
+from django.db import models
+from django.template.loader import render_to_string
+
+from core.models import Config
+from stator.models import State, StateField, StateGraph, StatorModel
+
+
+class PasswordResetStates(StateGraph):
+ new = State(try_interval=3)
+ sent = State()
+
+ new.transitions_to(sent)
+
+ @classmethod
+ async def handle_new(cls, instance: "PasswordReset"):
+ """
+ Sends the password reset email.
+ """
+ reset = await instance.afetch_full()
+ if reset.new_account:
+ await sync_to_async(send_mail)(
+ subject=f"{Config.system.site_name}: Confirm new account",
+ message=render_to_string(
+ "emails/new_account.txt",
+ {
+ "reset": reset,
+ "config": Config.system,
+ "settings": settings,
+ },
+ ),
+ from_email=settings.EMAIL_FROM,
+ recipient_list=[reset.user.email],
+ )
+ else:
+ await sync_to_async(send_mail)(
+ subject=f"{Config.system.site_name}: Reset password",
+ message=render_to_string(
+ "emails/password_reset.txt",
+ {
+ "reset": reset,
+ "config": Config.system,
+ "settings": settings,
+ },
+ ),
+ from_email=settings.EMAIL_FROM,
+ recipient_list=[reset.user.email],
+ )
+ return cls.sent
+
+
+class PasswordReset(StatorModel):
+ """
+ A password reset for a user (this is also how we create accounts)
+ """
+
+ state = StateField(PasswordResetStates)
+
+ user = models.ForeignKey(
+ "users.user",
+ on_delete=models.CASCADE,
+ related_name="password_resets",
+ )
+
+ token = models.CharField(max_length=500, unique=True)
+ new_account = models.BooleanField()
+
+ created = models.DateTimeField(auto_now_add=True)
+ updated = models.DateTimeField(auto_now=True)
+
+ @classmethod
+ def create_for_user(cls, user):
+ return cls.objects.create(
+ user=user,
+ token="".join(random.choice(string.ascii_lowercase) for i in range(42)),
+ new_account=not user.password,
+ )
+
+ ### Async helpers ###
+
+ async def afetch_full(self):
+ """
+ Returns a version of the object with all relations pre-loaded
+ """
+ return await PasswordReset.objects.select_related(
+ "user",
+ ).aget(pk=self.pk)