add forgot password page

This commit is contained in:
David Rodenkirchen 2024-08-26 23:31:27 +02:00
parent 4a6b09f41c
commit 704184d6f9
5 changed files with 128 additions and 17 deletions

Binary file not shown.

View File

@ -104,7 +104,7 @@ if __name__ == "__main__":
Page( Page(
name="ForgotPassword", name="ForgotPassword",
page_url="forgot-password", page_url="forgot-password",
build=lambda: pages.PlaceholderPage(placeholder_name="Passwort vergessen"), build=pages.ForgotPasswordPage,
), ),
Page( Page(
name="EditProfile", name="EditProfile",

View File

@ -0,0 +1,111 @@
from hashlib import sha256
from random import choices
from email_validator import validate_email, EmailNotValidError
from rio import Column, Component, event, Text, TextStyle, TextInput, TextInputChangeEvent, Button
from src.ez_lan_manager import ConfigurationService, UserService, MailingService
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ez_lan_manager.pages import BasePage
class ForgotPasswordPage(Component):
def on_email_changed(self, change_event: TextInputChangeEvent) -> None:
try:
validate_email(change_event.text, check_deliverability=False)
self.email_input.is_valid = True
self.submit_button.is_sensitive = True
except EmailNotValidError:
self.email_input.is_valid = False
self.submit_button.is_sensitive = False
async def on_submit_button_pressed(self) -> None:
self.submit_button.is_loading = True
await self.submit_button.force_refresh()
lan_info = self.session[ConfigurationService].get_lan_info()
user_service = self.session[UserService]
mailing_service = self.session[MailingService]
user = user_service.get_user(self.email_input.text.strip())
if user is not None:
new_password = "".join(choices(user_service.ALLOWED_USER_NAME_SYMBOLS, k=16))
user.user_password = sha256(new_password.encode(encoding="utf-8")).hexdigest()
user_service.update_user(user)
await mailing_service.send_email(
subject=f"Dein neues Passwort für {lan_info.name}",
body=f"Du hast für den EZ-LAN Manager der {lan_info.name} ein neues Passwort angefragt. "
f"Und hier ist es schon:\n\n{new_password}\n\nSolltest du kein neues Passwort angefordert haben, "
f"ignoriere diese E-Mail.\n\nLiebe Grüße\nDein {lan_info.name} - Team",
receiver=self.email_input.text.strip()
)
self.submit_button.is_loading = False
self.email_input.text = ""
self.info_text.text = "Falls für diese E-Mail ein Konto besteht, " \
"bekommst du in den nächsten Minuten ein neues Passwort zugeschickt. " \
"Bitte prüfe dein Spam-Postfach.",
@event.on_populate
async def on_populate(self) -> None:
await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Passwort vergessen")
def build(self) -> Component:
self.email_input = TextInput(
label="E-Mail Adresse",
text="",
margin_left=1,
margin_right=1,
margin_bottom=1,
grow_x=True,
on_change=self.on_email_changed
)
self.submit_button = Button(
content=Text(
"Neues Passwort anfordern",
style=TextStyle(fill=self.session.theme.background_color, font_size=0.9),
align_x=0.5
),
grow_x=True,
margin_top=2,
margin_left=1,
margin_right=1,
margin_bottom=1,
shape="rectangle",
style="minor",
color=self.session.theme.secondary_color,
on_press=self.on_submit_button_pressed,
is_sensitive=False
)
self.info_text = Text(
text="",
style=TextStyle(
fill=self.session.theme.background_color,
font_size=1
),
margin_top=2,
margin_left=1,
margin_right=1,
margin_bottom=2,
wrap=True
)
return BasePage(
content=Column(
MainViewContentBox(
content=Column(
Text(
"Passwort vergessen",
style=TextStyle(
fill=self.session.theme.background_color,
font_size=1.2
),
margin_top=2,
margin_bottom=2,
align_x=0.5
),
self.email_input,
self.submit_button,
self.info_text
)
),
align_y=0,
)
)

View File

@ -4,3 +4,4 @@ from .PlaceholderPage import PlaceholderPage
from .Logout import LogoutPage from .Logout import LogoutPage
from .Account import AccountPage from .Account import AccountPage
from .EditProfile import EditProfilePage from .EditProfile import EditProfilePage
from .ForgotPassword import ForgotPasswordPage

View File

@ -1,7 +1,6 @@
import logging import logging
from email.mime.multipart import MIMEMultipart from email.message import EmailMessage
from email.mime.text import MIMEText import aiosmtplib
from smtplib import SMTP
from src.ez_lan_manager.types.ConfigurationTypes import MailingServiceConfiguration from src.ez_lan_manager.types.ConfigurationTypes import MailingServiceConfiguration
@ -11,20 +10,20 @@ class MailingService:
def __init__(self, configuration: MailingServiceConfiguration): def __init__(self, configuration: MailingServiceConfiguration):
self._config = configuration self._config = configuration
def send_email(self, subject: str, body: str, receiver: str) -> None: async def send_email(self, subject: str, body: str, receiver: str) -> None:
# ToDo: Check with Rio/FastAPI if this needs to be ASYNC
try: try:
msg = MIMEMultipart() message = EmailMessage()
msg['From'] = self._config.sender message["From"] = self._config.sender
msg['To'] = receiver message["To"] = receiver
msg['Subject'] = subject message["Subject"] = subject
message.set_content(body)
msg.attach(MIMEText(body, 'plain'))
with SMTP(self._config.smtp_server, self._config.smtp_port) as server:
server.starttls()
server.login(self._config.username, self._config.password)
server.sendmail(self._config.sender, receiver, msg.as_string())
await aiosmtplib.send(
message,
hostname=self._config.smtp_server,
port=self._config.smtp_port,
username=self._config.username,
password=self._config.password
)
except Exception as e: except Exception as e:
logger.error(f"Failed to send email: {e}") logger.error(f"Failed to send email: {e}")