From e6b7f4ca85b6883cb495e623e49c7529fccdd774 Mon Sep 17 00:00:00 2001 From: David Rodenkirchen Date: Sun, 25 Aug 2024 22:31:29 +0200 Subject: [PATCH] add login --- src/EzLanManager.py | 19 +++- .../components/DesktopNavigation.py | 36 +++---- .../components/DesktopNavigationButton.py | 24 +++++ src/ez_lan_manager/components/LoginBox.py | 95 ++++++++++++------- src/ez_lan_manager/components/UserInfoBox.py | 63 ++++++++++++ .../components/UserInfoBoxButton.py | 24 +++++ src/ez_lan_manager/services/UserService.py | 8 +- src/ez_lan_manager/types/SessionStorage.py | 7 ++ 8 files changed, 216 insertions(+), 60 deletions(-) create mode 100644 src/ez_lan_manager/components/DesktopNavigationButton.py create mode 100644 src/ez_lan_manager/components/UserInfoBox.py create mode 100644 src/ez_lan_manager/components/UserInfoBoxButton.py create mode 100644 src/ez_lan_manager/types/SessionStorage.py diff --git a/src/EzLanManager.py b/src/EzLanManager.py index 5840aa0..612ed00 100644 --- a/src/EzLanManager.py +++ b/src/EzLanManager.py @@ -2,10 +2,11 @@ import logging from pathlib import Path -from rio import App, Theme, Color, Font, Page +from rio import App, Theme, Color, Font, Page, Session from from_root import from_root from src.ez_lan_manager import pages, init_services +from src.ez_lan_manager.types.SessionStorage import SessionStorage logger = logging.getLogger(__name__.split(".")[-1]) @@ -27,6 +28,10 @@ if __name__ == "__main__": services = init_services() lan_info = services[2].get_lan_info() + async def on_session_start(session: Session) -> None: + await session.set_title(lan_info.name) + session.attach(SessionStorage()) + app = App( name="EZ LAN Manager", pages=[ @@ -89,12 +94,22 @@ if __name__ == "__main__": name="Imprint", page_url="imprint", build=lambda: pages.PlaceholderPage(placeholder_name="Impressum & DSGVO"), + ), + Page( + name="Register", + page_url="register", + build=lambda: pages.PlaceholderPage(placeholder_name="Registrierung"), + ), + Page( + name="ForgotPassword", + page_url="forgot-password", + build=lambda: pages.PlaceholderPage(placeholder_name="Passwort vergessen"), ) ], theme=theme, assets_dir=Path(__file__).parent / "assets", default_attachments=services, - on_session_start= lambda s: s.set_title(lan_info.name), + on_session_start=on_session_start, icon=from_root("src/ez_lan_manager/assets/img/favicon.png"), meta_tags={ "robots": "INDEX,FOLLOW", diff --git a/src/ez_lan_manager/components/DesktopNavigation.py b/src/ez_lan_manager/components/DesktopNavigation.py index 58842e5..036f645 100644 --- a/src/ez_lan_manager/components/DesktopNavigation.py +++ b/src/ez_lan_manager/components/DesktopNavigation.py @@ -1,40 +1,26 @@ from rio import * from src.ez_lan_manager import ConfigurationService +from src.ez_lan_manager.components.DesktopNavigationButton import DesktopNavigationButton from src.ez_lan_manager.components.LoginBox import LoginBox - - -class DesktopNavigationButton(Component): - STYLE = TextStyle(fill=Color.from_hex("02dac5"), font_size=0.9) - label: str - target_url: str - open_new_tab: bool = False - - def build(self) -> Component: - return Link( - content=Button( - content=Text(self.label, style=self.STYLE), - shape="rectangle", - style="minor", - color="secondary", - grow_x=True, - margin_left=0.6, - margin_right=0.6, - margin_top=0.6 - ), - target_url=self.target_url, - open_in_new_tab=self.open_new_tab - ) - +from src.ez_lan_manager.components.UserInfoBox import UserInfoBox +from src.ez_lan_manager.types.SessionStorage import SessionStorage class DesktopNavigation(Component): + async def refresh_cb(self) -> None: + self.box = self.login_box if self.session[SessionStorage].user_id is None else self.user_info_box + await self.force_refresh() + def build(self) -> Component: + self.user_info_box = UserInfoBox() + self.login_box = LoginBox(self.refresh_cb) + self.box = self.login_box if self.session[SessionStorage].user_id is None else self.user_info_box lan_info = self.session[ConfigurationService].get_lan_info() return Card( Column( Text(lan_info.name, align_x=0.5, margin_top=0.3, style=TextStyle(fill=self.session.theme.hud_color, font_size=2.5)), Text(f"Edition {lan_info.iteration}", align_x=0.5, style=TextStyle(fill=self.session.theme.hud_color, font_size=1.2), margin_top=0.3, margin_bottom=2), - LoginBox(), + self.box, DesktopNavigationButton("News", "./news"), Spacer(min_height=1), DesktopNavigationButton(f"Über {lan_info.name} {lan_info.iteration}", "./overview"), diff --git a/src/ez_lan_manager/components/DesktopNavigationButton.py b/src/ez_lan_manager/components/DesktopNavigationButton.py new file mode 100644 index 0000000..b59ea3b --- /dev/null +++ b/src/ez_lan_manager/components/DesktopNavigationButton.py @@ -0,0 +1,24 @@ +from rio import Component, TextStyle, Color, Link, Button, Text + + +class DesktopNavigationButton(Component): + STYLE = TextStyle(fill=Color.from_hex("02dac5"), font_size=0.9) + label: str + target_url: str + open_new_tab: bool = False + + def build(self) -> Component: + return Link( + content=Button( + content=Text(self.label, style=self.STYLE), + shape="rectangle", + style="minor", + color="secondary", + grow_x=True, + margin_left=0.6, + margin_right=0.6, + margin_top=0.6 + ), + target_url=self.target_url, + open_in_new_tab=self.open_new_tab + ) diff --git a/src/ez_lan_manager/components/LoginBox.py b/src/ez_lan_manager/components/LoginBox.py index 01b97cf..2060932 100644 --- a/src/ez_lan_manager/components/LoginBox.py +++ b/src/ez_lan_manager/components/LoginBox.py @@ -1,47 +1,78 @@ -from rio import Component, Card, Column, Text, Row, Rectangle, Button, TextStyle, Color, Spacer, TextInput +from typing import Callable + +from rio import Component, Column, Text, Row, Rectangle, Button, TextStyle, Color, Spacer, TextInput + +from src.ez_lan_manager import UserService +from src.ez_lan_manager.types.SessionStorage import SessionStorage class LoginBox(Component): TEXT_STYLE = TextStyle(fill=Color.from_hex("02dac5"), font_size=0.9) + refresh_cb: Callable + + async def _on_login_pressed(self) -> None: + self.login_button.is_loading = True + user_name = self.user_name_input.text.lower() + if self.session[UserService].is_login_valid(user_name, self.password_input.text): + self.session[SessionStorage].user_id = self.session[UserService].get_user(user_name).user_id + self.user_name_input.is_valid = True + self.password_input.is_valid = True + self.login_button.is_loading = False + await self.refresh_cb() + else: + self.user_name_input.is_valid = False + self.password_input.is_valid = False + self.login_button.is_loading = False + def build(self) -> Component: + self.user_name_input = TextInput( + text="", + label="Benutzername", + accessibility_label = "Benutzername", + min_height=0.5, + on_confirm=lambda _: self._on_login_pressed() + ) + self.password_input = TextInput( + text="", + label="Passwort", + accessibility_label="Passwort", + is_secret=True, + on_confirm=lambda _: self._on_login_pressed() + ) + self.login_button = Button( + Text("LOGIN", style=self.TEXT_STYLE, justify="center"), + shape="rectangle", + style="minor", + color="secondary", + margin_bottom=0.4, + on_press=self._on_login_pressed + ) + self.register_button = Button( + Text("REG", style=self.TEXT_STYLE, justify="center"), + shape="rectangle", + style="minor", + color="secondary", + on_press=lambda: self.session.navigate_to("./register") + ) + self.forgot_password_button = Button( + Text("LST PWD",style=self.TEXT_STYLE, justify="center"), + shape="rectangle", + style="minor", + color="secondary", + on_press=lambda: self.session.navigate_to("./forgot-password") + ) return Rectangle( content=Column( - TextInput( - text="", - label="Benutzername", - accessibility_label = "Benutzername", - min_height=0.5 - ), - TextInput( - text="", - label="Passwort", - accessibility_label="Passwort", - is_secret=True - ), + self.user_name_input, + self.password_input, Column( Row( - Button( - Text("LOGIN", style=self.TEXT_STYLE, justify="center"), - shape="rectangle", - style="minor", - color="secondary", - margin_bottom=0.4 - ) + self.login_button ), Row( - Button( - Text("REG", style=self.TEXT_STYLE, justify="center"), - shape="rectangle", - style="minor", - color="secondary" - ), + self.register_button, Spacer(), - Button( - Text("LST PWD",style=self.TEXT_STYLE, justify="center"), - shape="rectangle", - style="minor", - color="secondary" - ), + self.forgot_password_button, proportions=(49, 2, 49) ) ), diff --git a/src/ez_lan_manager/components/UserInfoBox.py b/src/ez_lan_manager/components/UserInfoBox.py new file mode 100644 index 0000000..d9f7244 --- /dev/null +++ b/src/ez_lan_manager/components/UserInfoBox.py @@ -0,0 +1,63 @@ +from random import choice + +from rio import Component, Card, Column, Text, Row, Rectangle, Button, TextStyle, Color, Spacer, TextInput, Link + +from src.ez_lan_manager import UserService, AccountingService +from src.ez_lan_manager.components.UserInfoBoxButton import UserInfoBoxButton +from src.ez_lan_manager.types.SessionStorage import SessionStorage + +class StatusButton(Component): + STYLE = TextStyle(fill=Color.from_hex("121212"), font_size=0.5) + label: str + target_url: str + enabled: bool + + def build(self) -> Component: + return Link( + content=Button( + content=Text(self.label, style=self.STYLE, justify="center"), + shape="rectangle", + style="major", + color="success" if self.enabled else "danger", + grow_x=True, + margin_left=0.6, + margin_right=0.6, + margin_top=0.6 + ), + target_url=self.target_url, + align_y=0.5, + grow_y=False + ) + + +class UserInfoBox(Component): + @staticmethod + def get_greeting() -> str: + return choice(["Grüße", "Hallo", "Willkommen", "Moin", "Ahoi"]) + + def build(self) -> Component: + user = self.session[UserService].get_user(self.session[SessionStorage].user_id) + if user is None: # Noone logged in + return Text("") + a_s = self.session[AccountingService] + return Rectangle( + content=Column( + Text(f"{self.get_greeting()},", style=TextStyle(fill=Color.from_hex("02dac5"), font_size=0.9), justify="center"), + Text(f"{user.user_name}", style=TextStyle(fill=Color.from_hex("02dac5"), font_size=1.2), justify="center"), + Row( + StatusButton(label="TICKET", target_url="", enabled=True), + StatusButton(label="SITZPLATZ", target_url="", enabled=False), + proportions=(50, 50), + grow_y=False + ), + UserInfoBoxButton("Profil bearbeiten", "./edit-profile"), + UserInfoBoxButton(f"Guthaben: {a_s.make_euro_string_from_int(a_s.get_balance(user.user_id))}", "./account"), + UserInfoBoxButton("Ausloggen", "./logout") + ), + fill=Color.TRANSPARENT, + min_height=8, + min_width=12, + align_x=0.5, + margin_top=0.3, + margin_bottom=2 + ) diff --git a/src/ez_lan_manager/components/UserInfoBoxButton.py b/src/ez_lan_manager/components/UserInfoBoxButton.py new file mode 100644 index 0000000..bcaa0d1 --- /dev/null +++ b/src/ez_lan_manager/components/UserInfoBoxButton.py @@ -0,0 +1,24 @@ +from rio import Component, TextStyle, Color, Link, Button, Text + + +class UserInfoBoxButton(Component): + STYLE = TextStyle(fill=Color.from_hex("02dac5"), font_size=0.6) + label: str + target_url: str + open_new_tab: bool = False + + def build(self) -> Component: + return Link( + content=Button( + content=Text(self.label, style=self.STYLE), + shape="rectangle", + style="minor", + color="secondary", + grow_x=True, + margin_left=0.6, + margin_right=0.6, + margin_top=0.6 + ), + target_url=self.target_url, + open_in_new_tab=self.open_new_tab + ) diff --git a/src/ez_lan_manager/services/UserService.py b/src/ez_lan_manager/services/UserService.py index 59a66e5..2e4fc68 100644 --- a/src/ez_lan_manager/services/UserService.py +++ b/src/ez_lan_manager/services/UserService.py @@ -16,9 +16,12 @@ class UserService: def __init__(self, db_service: DatabaseService) -> None: self._db_service = db_service - def get_user(self, accessor: Union[str, int]) -> Optional[User]: + def get_user(self, accessor: Optional[Union[str, int]]) -> Optional[User]: + if accessor is None: + return if isinstance(accessor, int): return self._db_service.get_user_by_id(accessor) + accessor = accessor.lower() if "@" in accessor: return self._db_service.get_user_by_mail(accessor) return self._db_service.get_user_by_name(accessor) @@ -34,6 +37,8 @@ class UserService: if disallowed_char: raise NameNotAllowedError(disallowed_char) + user_name = user_name.lower() + hashed_pw = sha256(password_clear_text.encode(encoding="utf-8")).hexdigest() return self._db_service.create_user(user_name, user_mail, hashed_pw) @@ -41,6 +46,7 @@ class UserService: disallowed_char = self._check_for_disallowed_char(user.user_name) if disallowed_char: raise NameNotAllowedError(disallowed_char) + user.user_name = user.user_name.lower() return self._db_service.update_user(user) def is_login_valid(self, user_name_or_mail: str, password_clear_text: str) -> bool: diff --git a/src/ez_lan_manager/types/SessionStorage.py b/src/ez_lan_manager/types/SessionStorage.py new file mode 100644 index 0000000..2e4a140 --- /dev/null +++ b/src/ez_lan_manager/types/SessionStorage.py @@ -0,0 +1,7 @@ +from dataclasses import dataclass +from typing import Optional + + +@dataclass(frozen=False) +class SessionStorage: + user_id: Optional[int] = None # DEBUG: Put user ID here to skip login