diff --git a/requirements.txt b/requirements.txt index 5ab0094..ffb0c34 100644 Binary files a/requirements.txt and b/requirements.txt differ diff --git a/src/EzLanManager.py b/src/EzLanManager.py index 86395b4..3ac34e5 100644 --- a/src/EzLanManager.py +++ b/src/EzLanManager.py @@ -104,7 +104,7 @@ if __name__ == "__main__": Page( name="ForgotPassword", page_url="forgot-password", - build=lambda: pages.PlaceholderPage(placeholder_name="Passwort vergessen"), + build=pages.ForgotPasswordPage, ), Page( name="EditProfile", diff --git a/src/ez_lan_manager/pages/ForgotPassword.py b/src/ez_lan_manager/pages/ForgotPassword.py new file mode 100644 index 0000000..7dc0212 --- /dev/null +++ b/src/ez_lan_manager/pages/ForgotPassword.py @@ -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, + ) + ) diff --git a/src/ez_lan_manager/pages/__init__.py b/src/ez_lan_manager/pages/__init__.py index 6d6c35c..e11b3bb 100644 --- a/src/ez_lan_manager/pages/__init__.py +++ b/src/ez_lan_manager/pages/__init__.py @@ -4,3 +4,4 @@ from .PlaceholderPage import PlaceholderPage from .Logout import LogoutPage from .Account import AccountPage from .EditProfile import EditProfilePage +from .ForgotPassword import ForgotPasswordPage diff --git a/src/ez_lan_manager/services/MailingService.py b/src/ez_lan_manager/services/MailingService.py index f16e479..0231827 100644 --- a/src/ez_lan_manager/services/MailingService.py +++ b/src/ez_lan_manager/services/MailingService.py @@ -1,7 +1,6 @@ import logging -from email.mime.multipart import MIMEMultipart -from email.mime.text import MIMEText -from smtplib import SMTP +from email.message import EmailMessage +import aiosmtplib from src.ez_lan_manager.types.ConfigurationTypes import MailingServiceConfiguration @@ -11,20 +10,20 @@ class MailingService: def __init__(self, configuration: MailingServiceConfiguration): self._config = configuration - def send_email(self, subject: str, body: str, receiver: str) -> None: - # ToDo: Check with Rio/FastAPI if this needs to be ASYNC + async def send_email(self, subject: str, body: str, receiver: str) -> None: try: - msg = MIMEMultipart() - msg['From'] = self._config.sender - msg['To'] = receiver - msg['Subject'] = subject - - 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()) + message = EmailMessage() + message["From"] = self._config.sender + message["To"] = receiver + message["Subject"] = subject + message.set_content(body) + 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: logger.error(f"Failed to send email: {e}")